├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── attestation.js ├── index.js ├── permissions.js ├── record.js ├── records.js ├── stow.js └── util.js ├── stow-logo.jpg ├── test ├── .eslintrc ├── 1-util-test.js ├── 3-linnia-records-test.js ├── 4-linnia-permissions-test.js ├── 5-linnia-record-class-test.js └── deployForTests.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-runtime", 5 | "babel-plugin-add-module-exports" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:10.5 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | # start testrpc so that prepare runs without errors 30 | 31 | - run: npm ci 32 | 33 | - run: 34 | command: npm run testrpc 35 | background: true 36 | - run: sleep 5 37 | 38 | - save_cache: 39 | paths: 40 | - node_modules 41 | key: v1-dependencies-{{ checksum "package.json" }} 42 | 43 | # run tests! 44 | - run: npm test 45 | - run: npm run build 46 | 47 | - run: npm run lint 48 | 49 | # Teardown 50 | # If you break your build into multiple jobs with workflows, you will probably want to do the parts of this that are relevant in each 51 | # Save test results 52 | # - store_test_results: 53 | # path: coverage 54 | # # Save artifacts 55 | # - store_artifacts: 56 | # path: coverage 57 | - store_artifacts: 58 | path: dist 59 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.js] 10 | indent_size = 2 11 | indent_style = space 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /dist 3 | /node_modules 4 | /src/contracts 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": "airbnb", 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "rules": { 11 | "no-underscore-dangle": "off", 12 | "max-len": 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | 65 | # Babel and Webpack output 66 | lib 67 | dist 68 | 69 | # vscode 70 | .vscode 71 | 72 | #.DS_Store 73 | .DS_Store 74 | 75 | #src/DS_Store 76 | src/.DS_Store 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ConsenSys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [![NPM Package](https://img.shields.io/npm/v/@stowprotocol/stow-js.svg?style=flat-square)](https://www.npmjs.com/package/@stowprotocol/stow-js) ![Build Status](https://circleci.com/gh/ConsenSys/stow-js.png?circle-token=:circle-token&style=shield) 4 | 5 | # StowJS 6 | 7 | ## Quickstart 8 | 9 | ```javascript 10 | const Web3 = require("web3"); 11 | const Stow = require("@stowprotocol/stow-js"); 12 | 13 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); 14 | 15 | const stow = new Stow(web3); 16 | 17 | // get the deployed contracts 18 | const { _, users, records, permissions } = await stow.getContractInstances(); 19 | ``` 20 | 21 | ## Installation 22 | 23 | You can install using npm: 24 | 25 | ```bash 26 | npm i @stowprotocol/stow-js 27 | ``` 28 | 29 | or add inject the library onto the `window` using a script tag: 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ## Building 36 | 37 | ``` 38 | npm run build 39 | ``` 40 | 41 | The compiled library is generated in `lib`, which you can require by typing `require('./lib')` 42 | 43 | ## Setting up a Dev Environment 44 | 45 | New to ethereum and/or Stow? Head over to our [resources page](https://github.com/ConsenSys/stow-resources) to learn more about the protocol and how to set up your ethereum development environment. 46 | 47 | ## API Documentation 48 | 49 | ### Constructor 50 | 51 | ```javascript 52 | new Stow(web3 [, options]) 53 | ``` 54 | 55 | ### Parameters 56 | 57 | 1. `Object` - An instantiated web3 API object 58 | 1. `Object` - (Optional) Constructor options 59 | 60 | - `hubAddress`: `String` - Address of the StowHub. If not specified, library will use the address of the latest version of the contract deployed on the network. 61 | - `tokenAddress`:`String` - Address of the StowToken. If not specified, library will use the address of the latest version of the contract deployed on the network. 62 | ### Example 63 | 64 | ```javascript 65 | const Web3 = require("web3"); 66 | const Stow = require("@stowprotocol/stow-js"); 67 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); 68 | const stow = new Stow(web3); 69 | ``` 70 | 71 | ## stow.getContractInstances 72 | 73 | ```javascript 74 | stow.getContractInstances(); 75 | ``` 76 | 77 | Gets Stow contract instances, wrapped in truffle contract. 78 | 79 | ### Returns 80 | 81 | `Promise` - A promise when resolved returns an object with truffle Contract instances. 82 | 83 | - `_hub`: `Object` - DO NOT USE - INTERNAL - StowHub truffle contract instance 84 | - `users`: `Object` - StowUsers truffle contract instance 85 | - `records`: `Object` - StowRecords truffle contract instance 86 | - `permissions`: `Object` - StowPermissions truffle contract instance 87 | 88 | ### Example 89 | 90 | ```javascript 91 | stow.getContractInstances().then(instances => { 92 | let users = instances.users; 93 | let records = instances.records; 94 | let permissions = instances.permissions; 95 | }); 96 | ``` 97 | 98 | ## stow.getRecord 99 | 100 | ```javascript 101 | stow.getRecord(dataHash); 102 | ``` 103 | 104 | Gets a record from Stow by data hash 105 | 106 | ### Parameters 107 | 108 | 1. `String` - The data hash, hex-encoded, 0x prefixed 109 | 110 | ### Returns 111 | 112 | `Promise` - A promise when resolved returns an instance of Record class. 113 | 114 | - `owner`: `String` - Hex-encoded record owner address 115 | - `metadataHash`: `String` - Hex-encoded metadata hash 116 | - `sigCount`: `Object` - A bignumber object, the signature count 117 | - `irisScore`: `Object` - A bignumber object, the IRIS score 118 | - `dataUri`: `String` - URI of the data 119 | - `timestamp`: `Date` - The timestamp when the record is added to Stow 120 | 121 | ## stow.getAttestation 122 | 123 | ```javascript 124 | stow.getAttestation(dataHash, attesterAddress); 125 | ``` 126 | 127 | Gets the attestation of the data 128 | 129 | ### Parameters 130 | 131 | 1. `String` - The data hash, hex-encoded, 0x prefixed 132 | 1. `String` - The address of the attester 133 | 134 | ### Returns 135 | 136 | `Promise` - A promise when resolved returns true if the record is attested by the specified attester. 137 | 138 | ## stow.getPermission 139 | 140 | ```javascript 141 | stow.getPermission(dataHash, viewerAddress); 142 | ``` 143 | 144 | Gets the permission information of a record 145 | 146 | ### Parameters 147 | 148 | 1. `String` - The data hash, hex-encoded, 0x prefixed 149 | 1. `String` - The address of the data viewer 150 | 151 | ### Returns 152 | 153 | `Promise` - A promise when resolved returns a Stow Permission object. 154 | 155 | - `canAccess`: `Boolean` - True if the specified viewer is allowed to access the record 156 | - `dataUri`: `String` - The data URI of the shared record 157 | 158 | --- 159 | 160 | # Append data to Stow 161 | 162 | In order to append data to Stow, your web3 instance need to be able to sign with the private key of the owner of the data. 163 | 164 | ## stow.addRecordWithReward 165 | 166 | ```javascript 167 | stow.addRecordWithReward(dataHash, metadata, dataUri, ethParams); 168 | ``` 169 | 170 | Add record to Stow and receive a small reward of stow tokens. This requires an optional parameter 171 | to be set in the constructor as follows: 172 | 173 | `new Stow(web3, (tokenAddress: 0x4cdfbdec0aa003116bf030f249a8a7285cd6a184))` 174 | 175 | 176 | ### Parameters 177 | 178 | 1. `String` - The data hash. Hash of the plain text data + metadata 179 | 2. `Object` - The metadata of the record. [Click here to read more about the metadata](https://github.com/ConsenSys/stow-resources/blob/master/METADATA.md) 180 | 3. `String` - The dataUri, link to the data (eg. the IPFS hash) 181 | 4. `Object` - The ethParams, ethereum account params. (The object need to contain the key 'from') 182 | 183 | ### Returns 184 | 185 | `Promise` - A promise when resolved returns the record object that was stored. 186 | 187 | ## stow.addRecord 188 | 189 | ```javascript 190 | stow.addRecord(dataHash, metadata, dataUri, ethParams); 191 | ``` 192 | 193 | Add record to Stow 194 | 195 | ### Parameters 196 | 197 | 1. `String` - The data hash. Hash of the plain text data + metadata 198 | 2. `Object` - The metadata of the record. [Click here to read more about the metadata](https://github.com/ConsenSys/stow-resources/blob/master/METADATA.md) 199 | 3. `String` - The dataUri, link to the data (eg. the IPFS hash) 200 | 4. `Object` - The ethParams, ethereum account params. (The object need to contain the key 'from') 201 | 202 | ### Returns 203 | 204 | `Promise` - A promise when resolved returns the record object that was stored. 205 | 206 | ### Append data using Stow Js in the browser 207 | 208 | If you are using a Stow Js in the browser you can create the Stow instance with the web3 object from Metamask and append a record the following way: 209 | 210 | ### Example 211 | 212 | ```javascript 213 | const Stow = require("@stowprotocol/stow-js"); 214 | const stow = new Stow(web3); 215 | 216 | const dataHash = "0xcc85fc3d763b9a1d83e4386b37b4b0f3daf9881638ba8b7db0c501c417acb689"; 217 | const metadata = { 218 | dataFormat: "json", 219 | domain: "social media", 220 | storage: "IPFS", 221 | encryptionScheme: "x25519-xsalsa20-poly1305", 222 | encryptionPublicKey: "hQYhHJpzZH/tGhz1wtqSjkL17tJSnEEC4yVGyNTHNQY=", 223 | stowjsVersion: "0.1.4", 224 | providerName: "SocialMedia", 225 | providerEthereumAddress: "0x349e31e92027f86b0ffeb5cd5e07003c7f229872", 226 | keywords: [ "socialmedia", "friends list", "people" ], 227 | creationDate: new Date(Date.UTC(96, 1, 2, 3, 4, 5)) 228 | }; 229 | const dataUri = "QmSg3jCiroFERczWdpFJUau5CofHfMKCSm5vZXSzn7sZGW"; 230 | const ethParams = { 231 | from: "0xb717d7adf0d19f5f48bb7ff0030e30fcd19eed72", gas: 500000, gasPrice: 20000000000 232 | }; 233 | 234 | const record = await stow.addRecord(dataHash, metadata, dataUri, ethParams); 235 | ``` 236 | 237 | In the example above the dataUri is the IPFS Hash where the file was stored. 238 | 239 | ### Append data using Stow Js outside of the browser 240 | 241 | In order to add a file you need to generate a web3 instance that can handle the private keys of the owner of the file. 242 | 243 | ### Example 244 | 245 | ```javascript 246 | const Stow = require("@stowprotocol/stow-js"); 247 | const HDWalletProvider = require('truffle-hdwallet-provider'); 248 | 249 | // HERE YOU NEED TO ADD THE PRIVATE KEYS OF THE OWNERS 250 | const privKeys = [privkey1, privkey2, ...] 251 | 252 | // HERE YOU NEED TO ADD THE PROVIDER, YOU COULD GET AND INFURA KEY AND PUT IT BELOW 253 | // OR USE ANY ETH PROVIDER 254 | const provider = 'https://ropsten.infura.io/INFURA_KEY' 255 | const hdWalletProvider = new HDWalletProvider(privKeys, provider); 256 | const web3 = new Web3(hdWalletProvider); 257 | 258 | const stow = new Stow(web3); 259 | 260 | const dataHash = "0xcc85fc3d763b9a1d83e4386b37b4b0f3daf9881638ba8b7db0c501c417acb689"; 261 | const metadata = { 262 | dataFormat: "json", 263 | domain: "social media", 264 | storage: "IPFS", 265 | encryptionScheme: "x25519-xsalsa20-poly1305", 266 | encryptionPublicKey: "hQYhHJpzZH/tGhz1wtqSjkL17tJSnEEC4yVGyNTHNQY=", 267 | stowjsVersion: "0.1.4", 268 | providerName: "SocialMedia", 269 | providerEthereumAddress: "0x349e31e92027f86b0ffeb5cd5e07003c7f229872", 270 | keywords: [ "socialmedia", "friends list", "people" ], 271 | creationDate: new Date(Date.UTC(96, 1, 2, 3, 4, 5)) 272 | }; 273 | const dataUri = "QmSg3jCiroFERczWdpFJUau5CofHfMKCSm5vZXSzn7sZGW"; 274 | const ethParams = { 275 | from: "0xb717d7adf0d19f5f48bb7ff0030e30fcd19eed72", gas: 500000, gasPrice: 20000000000 276 | }; 277 | 278 | const record = await stow.addRecord(dataHash, metadata, dataUri, ethParams); 279 | ``` 280 | 281 | # Attest data on Stow 282 | 283 | Attest data on Stow means sign a record and verify that contain legitimate and correct information. 284 | 285 | ## stow.signRecord 286 | 287 | ```javascript 288 | stow.signRecord(dataHash, ethParams); 289 | ``` 290 | 291 | Attest a record to Stow 292 | 293 | ### Parameters 294 | 295 | 1. `String` - The data hash, hex-encoded, 0x prefixed 296 | 2. `Object` - The ethParams, ethereum account params. (The object need to contain the key 'from') 297 | 298 | ### Returns 299 | 300 | `Promise` - A promise when resolved returns the Attestation that was added. 301 | 302 | ### Example 303 | 304 | ```javascript 305 | const Stow = require("@stowprotocol/stow-js"); 306 | const stow = new Stow(web3); 307 | 308 | const dataHash = "0xcc85fc3d763b9a1d83e4386b37b4b0f3daf9881638ba8b7db0c501c417acb689"; 309 | const ethParams = { 310 | from: "0xb717d7adf0d19f5f48bb7ff0030e30fcd19eed72", gas: 500000, gasPrice: 20000000000 311 | }; 312 | 313 | const attestation = await stow.signRecord(dataHash, ethParams); 314 | ``` 315 | 316 | ------ 317 | 318 | 319 | 320 | # Record class 321 | 322 | An instance of Record class is returned when `stow.getRecord` is called and promise resolved. 323 | 324 | ## Members 325 | 326 | - `owner`: `String` - Hex-encoded record owner address 327 | - `metadataHash`: `String` - Hex-encoded metadata hash 328 | - `sigCount`: `Object` - A bignumber object, the signature count 329 | - `irisScore`: `Object` - A bignumber object, the IRIS score 330 | - `dataUri`: `String` - URI of the data 331 | - `timestamp`: `Date` - The timestamp when the record is added to Stow 332 | 333 | ## record.getAttestation 334 | 335 | ```javascript 336 | record.getAttestation(attesterAddress); 337 | ``` 338 | 339 | Gets the attestation of the data 340 | 341 | ### Parameters 342 | 343 | 1. `String` - The address of the attester 344 | 345 | ### Returns 346 | 347 | `Promise` - A promise when resolved returns true if the record is attested by the specified attester. 348 | 349 | ### Example 350 | 351 | ```javascript 352 | let dataHash = 353 | "0x174e6ab7cf9a53497cff763d0743258f5d5014cb20ae08c7ec22bf50f5d5e326"; 354 | let attester = "0xac07bea81fe26b379de0e4327f1a6ecd0875edfc"; 355 | stow 356 | .getRecord(dataHash) 357 | .then(record => record.getAttestation(attester)) 358 | .then(attested => { 359 | if (attested) { 360 | // ... 361 | } else { 362 | // ... 363 | } 364 | }); 365 | ``` 366 | 367 | ## record.getPermission 368 | 369 | ```javascript 370 | record.getPermission(viewerAddress); 371 | ``` 372 | 373 | Gets the permission information of a record 374 | 375 | ### Parameters 376 | 377 | 1. `String` - The address of the data viewer 378 | 379 | ### Returns 380 | 381 | `Promise` - A promise when resolved returns a Stow Permission object. 382 | 383 | - `canAccess`: `Boolean` - True if the specified viewer is allowed to access the record 384 | - `dataUri`: `String` - The data URI of the shared record 385 | 386 | ## record.decryptData 387 | 388 | ```javascript 389 | record.decryptData(privKey, uriResolver); 390 | ``` 391 | 392 | Gets the plaintext data of the record 393 | 394 | ### Parameters 395 | 396 | 1. `String` - The private key to decrypt the data 397 | 1. `(String) => (String)` - A function to resolve the data URI. The parameter is the string data URI. The function should return the plaintext data. 398 | 399 | ### Returns 400 | 401 | `String` - The plaintext data 402 | 403 | ### Example 404 | 405 | ```javascript 406 | let privKey = 407 | "0x5230a384e9d271d59a05a9d9f94b79cd98fcdcee488d1047c59057046e128d2b"; 408 | stow 409 | .decryptData(privKey, dataUri => { 410 | // assume data URI is HTTP URL here 411 | return fetch(dataUri).then(res => { 412 | return res.arrayBuffer(); 413 | }); 414 | }) 415 | .then(plainText => { 416 | console.log(plainText.toString()); 417 | }); 418 | ``` 419 | 420 | ## record.decryptPermissioned 421 | 422 | ```javascript 423 | record.decryptPermissioned(viewerAddress, privKey, uriResolver) 424 | ``` 425 | 426 | Gets the plaintext data of a permissioned copy of the record 427 | 428 | ### Parameters 429 | 430 | 1. `String` - The address of viewer 431 | 1. `String` - The private key to decrypt the data of the permissioned copy. Note that this is the key controlled by the viewer, not the record owner. 432 | 1. `(String) => (Promise)` - A function to resolve the data URI. The parameter is the string data URI. The function should return the encrypted data.. 433 | 434 | ### Returns 435 | 436 | `String` - The plaintext data 437 | 438 | ## record.verifyData 439 | 440 | ```javascript 441 | record.verifyData(plaintext); 442 | ``` 443 | 444 | Verifies the hash of the data against the one in Stow. 445 | 446 | ### Parameters 447 | 448 | 1. `String` - The plaintext data to be verified 449 | 450 | ### Returns 451 | 452 | `Boolean` - True if hash matches 453 | 454 | ## record.reencryptData 455 | 456 | ```javascript 457 | record.reencryptData(pubKey, privKey, uriResolver); 458 | ``` 459 | 460 | Re-encrypts the data to another public key 461 | 462 | ### Parameters 463 | 464 | 1. `String` - Public key to re-encrypt the data to 465 | 1. `String` - Private key to decrypt the record data. This should be a key controlled by the record owner 466 | 1. `(String) => (String)` - A function to resolve the data URI. The parameter is the string data URI. The function should return the encrypted data. 467 | 468 | ### Returns 469 | 470 | `String` - The re-encrypted data 471 | 472 | --- 473 | 474 | # Attestation class 475 | 476 | ## Members 477 | 478 | - `attester`: `String` - Hex-encoded attester address 479 | - `dataHash`: `String` - Hex-encoded data hash 480 | 481 | ------ 482 | 483 | 484 | 485 | # Utility functions 486 | 487 | ## Stow.util.encrypt 488 | 489 | ```javascript 490 | Stow.util.encrypt(pubKeyTo, plaintext) 491 | ``` 492 | 493 | Encrypts a message. 494 | 495 | ### Parameters 496 | 497 | 1. `String` - The public key to encrypt the data 498 | 1. `String` - The plaintext data 499 | 500 | ### Returns 501 | 502 | `String` - The encrypted data, which includes the IV, ephemeral public key, MAC, and ciphertext. 503 | 504 | ### Example 505 | 506 | ```javascript 507 | let pubKey = 508 | "0xb1f26f98d374540eac3d31208f13a3935318e228207084c9ee32d741ff1ad2341af4ac9658aba4a254bf1dc6451b3c08524febba5273bec227c73e25cd376387"; 509 | let encrypted = Stow.util.encrypt(pubKey, "foo"); 510 | console.log(encrypted.toString("hex")); 511 | ``` 512 | 513 | ## Stow.util.decrypt 514 | 515 | ```javascript 516 | Stow.util.decrypt(privKey, ciphertext); 517 | ``` 518 | 519 | Decrypts a message encrypted by `Stow.util.encrypt`. 520 | 521 | ### Parameters 522 | 523 | 1. `String` - The private key to decrypt the data. 524 | 1. `String` - The encrypted data, which includes the IV, ephemeral public key, MAC, and ciphertext. 525 | 526 | ### Returns 527 | 528 | `String` - The decrypted plaintext 529 | 530 | ### Example 531 | 532 | ```javascript 533 | let encrypted = 534 | "0xbf18f1b6eb4b748b18cc3bd4a8d47f5f045766a445431dd918a43d6ca7871bdf7acd2214dce02a508a97f173f0697e781cf3cbf1b2d6fc0dcce940cdcef0aab443469773eb672b04117d4cb36336891aa98cd21f07d994b756f456f52db2b26a316fdbaaf87f52a638e0ad4d4280b63ec6447befdc97ecf07117bfc9eb8f8a073f"; 535 | let privKey = 536 | "0x5230a384e9d271d59a05a9d9f94b79cd98fcdcee488d1047c59057046e128d2b"; 537 | let plaintext = Stow.util.decrypt(privKey, encrypted).toString(); 538 | // plaintext is 'foo' 539 | ``` 540 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stowprotocol/stow-js", 3 | "version": "0.4.0", 4 | "description": "Stow JavaScript API", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*", 8 | "src/*" 9 | ], 10 | "scripts": { 11 | "build": "babel src -d lib -s --copy-files", 12 | "lint": "eslint src test", 13 | "testrpc": "ganache-cli -p 7545 -i 5777", 14 | "test": "mocha --require babel-register", 15 | "cdn-build": "webpack" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ConsenSys/stow-js.git" 20 | }, 21 | "author": "Stow", 22 | "license": "Apache-2.0", 23 | "bugs": { 24 | "url": "https://github.com/ConsenSys/stow-js/issues" 25 | }, 26 | "homepage": "https://github.com/ConsenSys/stow-js#readme", 27 | "dependencies": { 28 | "@stowprotocol/stow-smart-contracts": "^0.1.9", 29 | "@stowprotocol/stow-addresses": "^0.4.0", 30 | "babel-runtime": "^6.26.0", 31 | "bignumber.js": "^7.2.1", 32 | "eth-sig-util": "^2.1.0", 33 | "ethereumjs-util": "^5.2.0", 34 | "truffle-contract": "3.0.5", 35 | "tweetnacl": "^1.0.0", 36 | "tweetnacl-util": "^0.15.0", 37 | "web3": "^1.0.0-beta.34", 38 | "websocket": "^1.0.26" 39 | }, 40 | "devDependencies": { 41 | "babel-cli": "^6.26.0", 42 | "babel-core": "^6.26.3", 43 | "babel-loader": "^7.1.4", 44 | "babel-plugin-add-module-exports": "^0.2.1", 45 | "babel-plugin-transform-runtime": "^6.23.0", 46 | "babel-preset-env": "^1.7.0", 47 | "babel-register": "^6.26.0", 48 | "chai": "^4.1.2", 49 | "eslint": "^4.19.1", 50 | "eslint-config-airbnb": "^16.1.0", 51 | "eslint-loader": "^2.0.0", 52 | "eslint-plugin-import": "^2.12.0", 53 | "eslint-plugin-jsx-a11y": "^6.0.3", 54 | "eslint-plugin-react": "^7.8.2", 55 | "ganache-cli": "^6.1.2", 56 | "mocha": "^5.2.0", 57 | "web3": "^1.0.0-beta.34", 58 | "webpack": "^4.10.2", 59 | "webpack-cli": "^2.1.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/attestation.js: -------------------------------------------------------------------------------- 1 | class Attestation { 2 | constructor(attester, dataHash) { 3 | this.attester = attester; 4 | this.dataHash = dataHash; 5 | } 6 | } 7 | 8 | export default Attestation; 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import stow from './stow'; 2 | 3 | export default stow; 4 | -------------------------------------------------------------------------------- /src/permissions.js: -------------------------------------------------------------------------------- 1 | const getPermission = async (permissionsContract, dataHash, viewer) => { 2 | const res = await permissionsContract.permissions.call(dataHash, viewer); 3 | return { 4 | canAccess: res[0], 5 | dataUri: res[1], 6 | }; 7 | }; 8 | 9 | export default { 10 | getPermission, 11 | }; 12 | -------------------------------------------------------------------------------- /src/record.js: -------------------------------------------------------------------------------- 1 | import eutil from 'ethereumjs-util'; 2 | import _recordsFunctions from './records'; 3 | import _permissionsFunctions from './permissions'; 4 | import _util from './util'; 5 | 6 | class Record { 7 | constructor( 8 | contracts, dataHash, owner, metadataHash, sigCount, irisScore, 9 | dataUri, timestamp, 10 | ) { 11 | this.contracts = contracts; 12 | this.dataHash = dataHash; 13 | this.owner = owner; 14 | this.metadataHash = metadataHash; 15 | this.sigCount = sigCount; 16 | this.irisScore = irisScore; 17 | this.dataUri = dataUri; 18 | this.timestamp = timestamp; 19 | } 20 | 21 | /** 22 | * Get the attestation of the record from a specified attester 23 | * @param {String} attester Address of the attester 24 | * @returns {Promise} 25 | */ 26 | async getAttestation(attester) { 27 | return _recordsFunctions.getAttestation(this.contracts.records, this.dataHash, attester); 28 | } 29 | 30 | /** 31 | * Get the permission of the record for a viewer 32 | * @param {String} viewer Address of the viewer 33 | * @returns {Promise<{canAccess: Boolean, dataUri: String}>} 34 | */ 35 | async getPermission(viewer) { 36 | return _permissionsFunctions.getPermission(this.contracts.permissions, this.dataHash, viewer); 37 | } 38 | 39 | /** 40 | * Gets the plaintext data of this record 41 | * @param {String} privKey Private key to decrypt the data 42 | * @param {Function} uriResolver Async function that takes a data URI string and returns the data 43 | * @returns {String} Plaintext data 44 | */ 45 | async decryptData(privKey, uriResolver) { 46 | const ciphertext = await uriResolver(this.dataUri); 47 | const plaintext = _util.decrypt(privKey, ciphertext); 48 | // check hash 49 | if (!this.verifyData(plaintext)) { 50 | throw new Error('plaintext data hash mismatch'); 51 | } 52 | return plaintext; 53 | } 54 | 55 | /** 56 | * Gets the plaintext data of a permissioned copy of the record 57 | * @param {String} viewer Address of the viewer 58 | * @param {String} privKey Private key to decrypt the data 59 | * @param {Function} uriResolver Async function that takes a data URI string and returns the data 60 | * @returns {String} Plaintext data 61 | */ 62 | async decryptPermissioned(viewer, privKey, uriResolver) { 63 | // get the permissioned data URI 64 | const perm = await this.getPermission(viewer); 65 | if (!perm.canAccess) { 66 | throw new Error('viewer has no permission to view the data'); 67 | } 68 | const ciphertext = await uriResolver(perm.dataUri); 69 | const plaintext = _util.decrypt(privKey, ciphertext); 70 | if (!this.verifyData(plaintext)) { 71 | throw new Error('plaintext data hash mismatch'); 72 | } 73 | return plaintext; 74 | } 75 | 76 | /** 77 | * Verifies data against the data hash in Stow 78 | * @param {Buffer|String} plaintext Plaintext data to be verified 79 | * @returns {Boolean} True if data hash matches 80 | */ 81 | verifyData(plaintext) { 82 | return eutil.bufferToHex(eutil.keccak256(plaintext)) === this.dataHash; 83 | } 84 | 85 | /** 86 | * Re-encrypts the data to another public key 87 | * @param {String} pubKey Public key to re-encrypt the data to 88 | * @param {String} privKey Private key to decrypt the record data 89 | * @param {Function} uriResolver Async function that takes a data URI string and returns the data 90 | * @returns {String} Re-encrypted data 91 | */ 92 | async reencryptData(pubKey, privKey, uriResolver) { 93 | const plaintext = await this.decryptData(privKey, uriResolver); 94 | const encryptedData = _util.encrypt(pubKey, plaintext); 95 | return encryptedData; 96 | } 97 | 98 | static async fromContract(recordsContract, permissionsContract, dataHash) { 99 | const r = await _recordsFunctions.getRecord(recordsContract, dataHash); 100 | const contracts = { 101 | records: recordsContract, 102 | permissions: permissionsContract, 103 | }; 104 | return new Record( 105 | contracts, dataHash, r.owner, r.metadataHash, 106 | r.sigCount, r.irisScore, r.dataUri, r.timestamp, 107 | ); 108 | } 109 | } 110 | 111 | export default Record; 112 | -------------------------------------------------------------------------------- /src/records.js: -------------------------------------------------------------------------------- 1 | import Attestation from './attestation'; 2 | 3 | const getRecord = async (recordsContract, dataHash) => { 4 | const res = await recordsContract.records.call(dataHash); 5 | const owner = res[0]; 6 | const metadataHash = res[1]; 7 | const sigCount = res[2]; 8 | const irisScore = res[3]; 9 | const dataUri = res[4]; 10 | const timestamp = new Date(res[5] * 1000); 11 | return { 12 | owner, 13 | metadataHash, 14 | sigCount, 15 | irisScore, 16 | dataUri, 17 | timestamp, 18 | }; 19 | }; 20 | 21 | const addRecordWithReward = async ( 22 | recordsContract, 23 | usersContract, 24 | dataHash, 25 | metadata, 26 | dataUri, 27 | tokenAddress, 28 | ethParams) => { 29 | if (!tokenAddress) { 30 | throw new Error('tokenAddress not valid. It is likely not set in stow constructor'); 31 | } 32 | 33 | // Check if there is from in the ethParams 34 | if (!ethParams.from) { 35 | throw new Error('ethParams object does not contain a "from" key'); 36 | } 37 | 38 | // Check if the owner is a Stow User 39 | const isUser = await usersContract.isUser(ethParams.from); 40 | if (!isUser) { 41 | throw new Error('the address is not registered in Stow'); 42 | } 43 | 44 | // If metadata is not JSON 45 | if (typeof metadata !== 'object') { 46 | throw new Error('Metadata has to be a JSON object'); 47 | } 48 | 49 | try { 50 | await recordsContract.addRecordwithReward(dataHash, JSON.stringify(metadata), dataUri, tokenAddress, ethParams); 51 | return getRecord(recordsContract, dataHash); 52 | } catch (e) { 53 | if (e.message === 'sender account not recognized') { 54 | throw new Error('The web3 Instance that you pass to Stow cannot sign a transaction for this address'); 55 | } else { 56 | throw e; 57 | } 58 | } 59 | }; 60 | 61 | const addRecord = async ( 62 | recordsContract, 63 | usersContract, 64 | dataHash, 65 | metadata, 66 | dataUri, 67 | ethParams) => { 68 | // Check if there is from in the ethParams 69 | if (!ethParams.from) { 70 | throw new Error('ethParams object does not contain a "from" key'); 71 | } 72 | 73 | // Check if the owner is a Stow User 74 | const isUser = await usersContract.isUser(ethParams.from); 75 | if (!isUser) { 76 | throw new Error('the address is not registered in Stow'); 77 | } 78 | 79 | // If metadata is not JSON 80 | if (typeof metadata !== 'object') { 81 | throw new Error('Metadata has to be a JSON object'); 82 | } 83 | 84 | try { 85 | await recordsContract.addRecord(dataHash, JSON.stringify(metadata), dataUri, ethParams); 86 | return getRecord(recordsContract, dataHash); 87 | } catch (e) { 88 | if (e.message === 'sender account not recognized') { 89 | throw new Error('The web3 Instance that you pass to Stow cannot sign a transaction for this address'); 90 | } else { 91 | throw e; 92 | } 93 | } 94 | }; 95 | 96 | const signRecord = async (recordsContract, usersContract, dataHash, ethParams) => { 97 | // Check if there is from in the ethParams 98 | if (!ethParams.from) { 99 | throw new Error('ethParams object does not contain a "from" key'); 100 | } 101 | 102 | // Check if the owner is a Stow User 103 | const isUser = await usersContract.isUser(ethParams.from); 104 | if (!isUser) { 105 | throw new Error('the address is not registered in Stow'); 106 | } 107 | 108 | // Check provenance of attester 109 | const provenance = await usersContract.provenanceOf(ethParams.from); 110 | if (!(provenance > 0)) { 111 | throw new Error('The attestor does not have provenance (Invalid Attester)'); 112 | } 113 | 114 | // Check if record exists 115 | const record = await getRecord(recordsContract, dataHash); 116 | if (record.owner === '0x0000000000000000000000000000000000000000') { 117 | throw new Error('The record does not exists'); 118 | } 119 | 120 | // Check if attester have signed the record already 121 | const sigExists = await recordsContract.sigExists(dataHash, ethParams.from); 122 | if (sigExists) { 123 | throw new Error('The attestor have already signed this record'); 124 | } 125 | 126 | try { 127 | await recordsContract.addSigByProvider(dataHash, ethParams); 128 | return new Attestation(ethParams.from, dataHash); 129 | } catch (e) { 130 | if (e.message === 'sender account not recognized') { 131 | throw new Error('The web3 Instance that you pass to Stow cannot sign a transaction for this address'); 132 | } else { 133 | throw e; 134 | } 135 | } 136 | }; 137 | 138 | const getAttestation = async ( 139 | recordsContract, dataHash, attester, 140 | ) => recordsContract.sigExists.call(dataHash, attester); 141 | 142 | export default { 143 | getRecord, 144 | addRecord, 145 | addRecordWithReward, 146 | getAttestation, 147 | signRecord, 148 | }; 149 | -------------------------------------------------------------------------------- /src/stow.js: -------------------------------------------------------------------------------- 1 | import TruffleContract from 'truffle-contract'; 2 | 3 | import StowAddresses from '@stowprotocol/stow-addresses'; 4 | import StowContractUpgradeHub from '@stowprotocol/stow-smart-contracts/build/contracts/StowHub.json'; 5 | import StowUsers from '@stowprotocol/stow-smart-contracts/build/contracts/StowUsers.json'; 6 | import StowRecords from '@stowprotocol/stow-smart-contracts/build/contracts/StowRecords.json'; 7 | import StowPermissions from '@stowprotocol/stow-smart-contracts/build/contracts/StowPermissions.json'; 8 | 9 | import Record from './record'; 10 | import _recordsFunctions from './records'; 11 | import _permissionsFunctions from './permissions'; 12 | import _util from './util'; 13 | 14 | /** 15 | * Stow API object 16 | */ 17 | class Stow { 18 | /** 19 | * Create a new Stow API object 20 | * @param {Object} web3 An instantiated web3 API object 21 | * @param {?{?hubAddress: String},?{?tokenAddress: String}} opt Optional constructor options 22 | * @returns {Stow} Created Stow API object 23 | */ 24 | constructor(web3, opt = {}) { 25 | this.web3 = web3; 26 | // truffle contracts 27 | const _hub = TruffleContract(StowContractUpgradeHub); 28 | const _users = TruffleContract(StowUsers); 29 | const _records = TruffleContract(StowRecords); 30 | const _permissions = TruffleContract(StowPermissions); 31 | _hub.setProvider(web3.currentProvider); 32 | _users.setProvider(web3.currentProvider); 33 | _records.setProvider(web3.currentProvider); 34 | _permissions.setProvider(web3.currentProvider); 35 | this._hub = _util.truffleHack(_hub); 36 | this._users = _util.truffleHack(_users); 37 | this._records = _util.truffleHack(_records); 38 | this._permissions = _util.truffleHack(_permissions); 39 | 40 | if (opt) { 41 | this._hubAddress = opt.hubAddress || undefined; 42 | this._tokenAddress = opt.tokenAddress || undefined; 43 | } 44 | 45 | this.network = new Promise((resolve) => { 46 | this.web3.eth.net.getId().then((netId) => { 47 | let network; 48 | switch (netId) { 49 | case 1: 50 | network = 'mainnet'; 51 | break; 52 | case 3: 53 | network = 'ropsten'; 54 | break; 55 | case 4: 56 | network = 'rinkeby'; 57 | break; 58 | default: 59 | network = 'unknown'; 60 | } 61 | 62 | if (StowAddresses[network]) { 63 | if (!this._hubAddress) this._hubAddress = StowAddresses[network].StowSmartContracts.latest; 64 | if (!this._tokenAddress) this._tokenAddress = StowAddresses[network].StowToken.latest; 65 | } 66 | 67 | if (!this._hubAddress) { 68 | throw new Error('Must specify Stow Hub address when using an unsupported network.'); 69 | } 70 | 71 | resolve(network); 72 | }); 73 | }); 74 | } 75 | 76 | /** 77 | * Get Stow contract instances, wrapped in truffle contract 78 | * @returns {Promise<{hub: Object, users: Object, records: Object, permissions: Object}>} 79 | */ 80 | async getContractInstances() { 81 | await this.network; 82 | const hubInstance = await this._getHubInstance(); 83 | const usersAddress = await hubInstance.usersContract(); 84 | const recordsAddress = await hubInstance.recordsContract(); 85 | const permissionsAddress = await hubInstance.permissionsContract(); 86 | return { 87 | _hub: hubInstance, 88 | users: await this._users.at(usersAddress), 89 | records: await this._records.at(recordsAddress), 90 | permissions: await this._permissions.at(permissionsAddress), 91 | }; 92 | } 93 | 94 | /** 95 | * Get a record from Stow by data hash 96 | * @param {String} dataHash hex-encoded data hash, 0x prefixed 97 | * @returns {Promise} 98 | */ 99 | async getRecord(dataHash) { 100 | const { records, permissions } = await this.getContractInstances(); 101 | return Record.fromContract(records, permissions, dataHash); 102 | } 103 | 104 | /** 105 | * Add a record from Stow by data hash 106 | * @param {String} dataHash hash of the plain text data + metadata 107 | * @param {Object} metadata public information about the data 108 | * @param {String} dataUri link to the data (eg. the IPFS hash) 109 | * @param {Object} ethParams ethereum account params 110 | * @returns {Promise} 111 | */ 112 | async addRecord(dataHash, metadata, dataUri, ethParams) { 113 | const { records, users } = await this.getContractInstances(); 114 | return _recordsFunctions.addRecord( 115 | records, 116 | users, 117 | dataHash, 118 | metadata, 119 | dataUri, 120 | ethParams, 121 | ); 122 | } 123 | 124 | /** 125 | * Add a record from Stow by data hash 126 | * @param {String} dataHash hash of the plain text data + metadata 127 | * @param {Object} metadata public information about the data 128 | * @param {String} dataUri link to the data (eg. the IPFS hash) 129 | * @param {Object} ethParams ethereum account params 130 | * @returns {Promise} 131 | */ 132 | async addRecordWithReward(dataHash, metadata, dataUri, ethParams) { 133 | const { records, users } = await this.getContractInstances(); 134 | return _recordsFunctions.addRecordWithReward( 135 | records, 136 | users, 137 | dataHash, 138 | metadata, 139 | dataUri, 140 | this._tokenAddress, 141 | ethParams, 142 | ); 143 | } 144 | 145 | /** 146 | * Get record attestation from Stow 147 | * @param {String} dataHash hex-encoded data hash, 0x prefixed 148 | * @returns {Promise} True if attested by specified user 149 | */ 150 | async getAttestation(dataHash, attesterAddress) { 151 | const { records } = await this.getContractInstances(); 152 | return _recordsFunctions.getAttestation(records, dataHash, attesterAddress); 153 | } 154 | 155 | /** 156 | * Sign a record (add attestation) 157 | * @param {String} dataHash hex-encoded data hash, 0x prefixed 158 | * @param {Object} ethParams ethereum account params 159 | * @returns {Promise} 160 | */ 161 | async signRecord(dataHash, ethParams) { 162 | const { records, users } = await this.getContractInstances(); 163 | return _recordsFunctions.signRecord(records, users, dataHash, ethParams); 164 | } 165 | 166 | /** 167 | * Get permission information of a record 168 | * @param {String} dataHash hex-encoded data hash, 0x prefixed 169 | * @param {String} viewerAddress hex-encoded ethereum address 170 | * @returns {Promise<{canAccess: Boolean, dataUri: String}>} 171 | */ 172 | async getPermission(dataHash, viewerAddress) { 173 | const { permissions } = await this.getContractInstances(); 174 | return _permissionsFunctions.getPermission(permissions, dataHash, viewerAddress); 175 | } 176 | 177 | /** 178 | * Internal DO NOT USE 179 | * @returns {Promise<*>} 180 | * @private 181 | */ 182 | async _getHubInstance() { 183 | // get hub contract instance 184 | // look up address either from user defined address or artifact 185 | if (this._hubAddress) { 186 | return this._hub.at(this._hubAddress); 187 | } 188 | return this._hub.deployed(); 189 | } 190 | } 191 | 192 | Stow.util = _util; 193 | 194 | export default Stow; 195 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | const nacl = require('tweetnacl'); 2 | nacl.util = require('tweetnacl-util'); 3 | const ethSigUtil = require('eth-sig-util'); 4 | 5 | const ALGO_VERSION = 'x25519-xsalsa20-poly1305'; 6 | 7 | /** 8 | * EIP 1098 (https://github.com/ethereum/EIPs/pull/1098) 9 | * Generate Keys 10 | * @returns {JSON} with publicKey and privateKey 11 | */ 12 | const genKeyPair = () => { 13 | const keys = nacl.box.keyPair(); 14 | return { 15 | privateKey: nacl.util.encodeBase64(keys.secretKey), 16 | publicKey: nacl.util.encodeBase64(keys.publicKey), 17 | }; 18 | }; 19 | 20 | /** 21 | * EIP 1098 (https://github.com/ethereum/EIPs/pull/1098) 22 | * Encrypt 23 | * @param {String} pubKeyTo 24 | * @param {JSON} data Data to be encrypted (Has to be JSON Object) 25 | * @returns {JSON} Encrypted message 26 | */ 27 | const encrypt = (pubKeyTo, data) => ethSigUtil.encryptSafely(pubKeyTo, { data }, ALGO_VERSION); 28 | 29 | /** 30 | * EIP 1098 (https://github.com/ethereum/EIPs/pull/1098) 31 | * Decrypt 32 | * @param {String} privKey 33 | * @param {String} encrypted Encrypted message 34 | * @returns {String} plaintext 35 | */ 36 | const decrypt = (privKey, encrypted) => ethSigUtil.decryptSafely(encrypted, nacl.util.decodeBase64(privKey)); 37 | 38 | /* eslint-disable */ 39 | 40 | const truffleHack = (contract) => { 41 | if (typeof contract.currentProvider.sendAsync !== 'function') { 42 | contract.currentProvider.sendAsync = function () { 43 | return contract.currentProvider.send.apply(contract.currentProvider, arguments); 44 | }; 45 | } 46 | return contract; 47 | }; 48 | 49 | /* eslint-enable */ 50 | 51 | export default { 52 | genKeyPair, 53 | encrypt, 54 | decrypt, 55 | truffleHack, 56 | }; 57 | -------------------------------------------------------------------------------- /stow-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsenSysMesh/stow-js/9667b0b01f2985aea63b101486e73ea20d433264/stow-logo.jpg -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true, 4 | "node": true 5 | }, 6 | "extends": "airbnb", 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "rules": { 11 | "no-underscore-dangle": "off", 12 | "max-len": 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/1-util-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { Buffer } from 'buffer'; 3 | import naclUtil from 'tweetnacl-util'; 4 | import Stow from '../src'; 5 | 6 | const DEFAULT_PADDING_LENGTH = (2 ** 11); 7 | 8 | describe('Encryption Scheme', () => { 9 | const privKey1 = 'wFGgG/Bv/36liIhdOGqH0TY5QpUVYkQP+Sdcbr1NgOI='; 10 | const pubKey1 = 'hQYhHJpzZH/tGhz1wtqSjkL17tJSnEEC4yVGyNTHNQY='; 11 | describe('roundtrip', () => { 12 | it('should encrypt and decrypt a string', () => { 13 | const data = '1'; 14 | const ct = Stow.util.encrypt(pubKey1, data); 15 | const pt = Stow.util.decrypt(privKey1, ct); 16 | assert.equal(pt.toString(), data); 17 | }); 18 | it('should encrypt so that the ciphertext aligns with filesystem blocks after decodeBase64', () => { 19 | const longString = 'O coz, coz, coz, my' + 20 | ' pretty little coz, that thou didst know how many fathom deep I am in love!' + 21 | ' But it cannot be sounded; my affection hath an unknown bottom, like the bay of Portugal.\n'; 22 | const shortString = 'f'; 23 | const ctLong = Stow.util.encrypt(pubKey1, longString.repeat(111)); 24 | const lenInBytes = Buffer.byteLength(naclUtil.decodeBase64(ctLong.ciphertext)); 25 | assert.equal(lenInBytes % (2 ** 11), 0, 'output should be divisable by 2k'); 26 | 27 | const ctShort = Stow.util.encrypt(pubKey1, shortString); 28 | const ctShortLenInBytes = Buffer.byteLength(naclUtil.decodeBase64(ctShort.ciphertext)); 29 | assert.equal(ctShortLenInBytes % (2 ** 11), 0, 'output should be divisable by 2k'); 30 | }); 31 | it('should encrypt padding is only added when neccessary', () => { 32 | // adjust for envelope an NACL extra bytes 33 | const ENVELOPE_BYTE_LEN = 40; 34 | const plainTextString = 'f'.repeat(DEFAULT_PADDING_LENGTH - ENVELOPE_BYTE_LEN); 35 | const plainTextLen = Buffer.byteLength(plainTextString); 36 | assert.equal(plainTextLen + ENVELOPE_BYTE_LEN, DEFAULT_PADDING_LENGTH, 'input envelope size is 2048'); 37 | const cipherText = Stow.util.encrypt(pubKey1, plainTextString); 38 | const lenInBytes = Buffer.byteLength(naclUtil.decodeBase64(cipherText.ciphertext)); 39 | assert.equal((lenInBytes % DEFAULT_PADDING_LENGTH), 0, 'output should be divisable by 2k'); 40 | }); 41 | it('should encrypt so that the string len that does not reveal input len', () => { 42 | const longString = 'O coz, coz, coz, my pretty little coz, that thou didst know how many fathom deep I am in love!' + 43 | ' But it cannot be sounded; my affection hath an unknown bottom, like the bay of Portugal.\n'; 44 | const shortString = 'f'; 45 | 46 | const ctShort = Stow.util.encrypt(pubKey1, shortString); 47 | const ctLong = Stow.util.encrypt(pubKey1, longString); 48 | assert.equal(ctShort.ciphertext.length, ctLong.ciphertext.length); 49 | }); 50 | }); 51 | 52 | describe('failure', () => { 53 | it('should fail when encrypt with empty key', () => { 54 | const data = 'foo'; 55 | assert.throws(() => Stow.util.encrypt('', data), Error, 'bad public key size'); 56 | }); 57 | it('should fail when encrypt with bad key', () => { 58 | const data = 'foo'; 59 | const pubKey2 = 'hQYhHJSjkL17VGyNTHNQY='; 60 | assert.throws(() => Stow.util.encrypt(pubKey2, data), Error, 'Bad public key'); 61 | }); 62 | it('should fail when decrypt with wrong key', () => { 63 | const data = 'foo'; 64 | const privWrongKey = '5VdzPXk23HBA+S1tcSsSFGxjPpsHgQ5PMx3tbfsxSIU='; 65 | const ct = Stow.util.encrypt(pubKey1, data); 66 | assert.throws(() => Stow.util.decrypt(privWrongKey, ct), Error, 'Decryption failed.'); 67 | }); 68 | it('should fail when decrypt with empty key', () => { 69 | const data = 'foo'; 70 | const ct = Stow.util.encrypt(pubKey1, data); 71 | assert.throws(() => Stow.util.decrypt('', ct), Error, 'bad secret key size'); 72 | }); 73 | it('should fail when encrypt object with toJSON', () => { 74 | const data = { toJSON: console.log }; 75 | assert.throws(() => Stow.util.encrypt(pubKey1, data), Error, 'Cannot encrypt with toJSON property. Please remove toJSON property'); 76 | }); 77 | it('should fail to decrypt when version is not supported', () => { 78 | const data = 'foo'; 79 | const ct = Stow.util.encrypt(pubKey1, data); 80 | ct.version = 'foobar'; 81 | assert.throws(() => Stow.util.decrypt(privKey1, ct), Error, 'Encryption type/version not supported.'); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/3-linnia-records-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import Web3 from 'web3'; 3 | import StowDeploy from './deployForTests'; 4 | 5 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 6 | const testDataHash = '0x276bc9ec8730ad53e827c0467c00473a53337e2cb4b61ada24760a217fb1ef14'; 7 | const testDataUri = 'QmbcfaK3bdpFTATifqXXLux4qx6CmgBUcd3fVMWxVszazP'; 8 | const testMetaData = 'Blood_Pressure'; 9 | 10 | // Add record should work 11 | const dataHash = '0xcc85fc3d763b9a1d83e4386b37b4b0f3daf9881638ba8b7db0c501c417acb689'; 12 | const metadata = { encryption: 'ecies', type: 'medical data' }; 13 | const dataUri = 'QmSg3jCiroFERczWdpFJUau5CofHfMKCSm5vZXSzn7sZGW'; 14 | 15 | // Add record should fail 16 | const dataHash2 = '0xcc85fc3d763b9a1d83e4386b37b4b0f3daf9881638ba8b7db0c501c417acb688'; 17 | const dataUri2 = 'QmSg3jCiroFERczWdpFJUau5CofHfMKCSm5vZXSzn5sZGW'; 18 | 19 | describe('Stow-records', async () => { 20 | const [admin, user, provider, provider2] = await web3.eth.getAccounts(); 21 | let stow; 22 | let contracts; 23 | let recordAddTime; 24 | beforeEach('deploy the contracts and set up roles', async () => { 25 | stow = await StowDeploy.deploy(web3, { 26 | from: admin, 27 | gas: 4000000, 28 | }); 29 | contracts = await stow.getContractInstances(); 30 | // add a user and 2 providers 31 | await contracts.users.register({ from: user }); 32 | await contracts.users.register({ from: provider }); 33 | await contracts.users.register({ from: provider2 }); 34 | await contracts.users.setProvenance(provider, 1, { from: admin }); 35 | await contracts.users.setProvenance(provider2, 1, { from: admin }); 36 | // append a file 37 | const tx = await contracts.records.addRecordByProvider( 38 | testDataHash, 39 | user, 40 | testMetaData, 41 | testDataUri, 42 | { 43 | from: provider, 44 | gas: 500000, 45 | }, 46 | ); 47 | const { blockNumber } = tx.receipt; 48 | const block = await web3.eth.getBlock(blockNumber); 49 | recordAddTime = block.timestamp; 50 | }); 51 | describe('get record', () => { 52 | it('should return the formatted data record', async () => { 53 | const record = await stow.getRecord(testDataHash); 54 | assert.equal(record.owner.toLowerCase(), user.toLowerCase()); 55 | assert.equal(record.metadataHash, web3.utils.sha3(testMetaData)); 56 | assert.equal(record.sigCount, 1); 57 | assert.equal(record.irisScore, 1); 58 | assert.equal(record.dataUri, testDataUri); 59 | assert.typeOf(record.timestamp, 'Date'); 60 | assert.equal(record.timestamp.getTime() / 1000, recordAddTime); 61 | }); 62 | }); 63 | describe('add record with reward', () => { 64 | it('should add the record', async () => { 65 | const ethParams = { from: user, gas: 500000, gasPrice: 20000000000 }; 66 | const record = await stow.addRecordWithReward(dataHash, metadata, dataUri, ethParams); 67 | assert.equal(record.owner.toLowerCase(), user.toLowerCase()); 68 | assert.equal(record.metadataHash, web3.utils.sha3(JSON.stringify(metadata))); 69 | assert.equal(record.dataUri, dataUri); 70 | }); 71 | it('should fail adding record, without a from user', async () => { 72 | const ethParams = { gas: 500000, gasPrice: 20000000000 }; 73 | try { 74 | await stow.addRecordWithReward(dataHash, metadata, dataUri, ethParams); 75 | } catch (e) { 76 | assert.equal(e.message, 'ethParams object does not contain a "from" key'); 77 | } 78 | }); 79 | it('should fail adding record, with a user that is not registered in Stow', async () => { 80 | const ethParams = { from: '0xb717d7adf0d17f5f48bb7ff0035e30fcd19eed72', gas: 500000, gasPrice: 20000000000 }; 81 | try { 82 | await stow.addRecordWithReward(dataHash, metadata, dataUri, ethParams); 83 | } catch (e) { 84 | assert.equal(e.message, 'the address is not registered in Stow'); 85 | } 86 | }); 87 | it('should fail adding record with metadata not JSON', async () => { 88 | const ethParams = { from: user, gas: 500000, gasPrice: 20000000000 }; 89 | try { 90 | await stow.addRecordWithReward(dataHash2, 'Sting Metadata', dataUri2, ethParams); 91 | } catch (e) { 92 | assert.equal(e.message, 'Metadata has to be a JSON object'); 93 | } 94 | }); 95 | }); 96 | describe('add record', () => { 97 | it('should add the record, with web3 instance with keys', async () => { 98 | const ethParams = { from: user, gas: 500000, gasPrice: 20000000000 }; 99 | const record = await stow.addRecord(dataHash, metadata, dataUri, ethParams); 100 | assert.equal(record.owner.toLowerCase(), user.toLowerCase()); 101 | assert.equal(record.metadataHash, web3.utils.sha3(JSON.stringify(metadata))); 102 | assert.equal(record.dataUri, dataUri); 103 | }); 104 | it('should fail adding record, without a from user', async () => { 105 | const ethParams = { gas: 500000, gasPrice: 20000000000 }; 106 | try { 107 | await stow.addRecord(dataHash, metadata, dataUri, ethParams); 108 | } catch (e) { 109 | assert.equal(e.message, 'ethParams object does not contain a "from" key'); 110 | } 111 | }); 112 | it('should fail adding record, with a user that is not registered in Stow', async () => { 113 | const ethParams = { from: '0xb717d7adf0d17f5f48bb7ff0035e30fcd19eed72', gas: 500000, gasPrice: 20000000000 }; 114 | try { 115 | await stow.addRecord(dataHash, metadata, dataUri, ethParams); 116 | } catch (e) { 117 | assert.equal(e.message, 'the address is not registered in Stow'); 118 | } 119 | }); 120 | it('should fail adding record with metadata not JSON', async () => { 121 | const ethParams = { from: user, gas: 500000, gasPrice: 20000000000 }; 122 | try { 123 | await stow.addRecord(dataHash2, 'Sting Metadata', dataUri2, ethParams); 124 | } catch (e) { 125 | assert.equal(e.message, 'Metadata has to be a JSON object'); 126 | } 127 | }); 128 | }); 129 | describe('get attestation', () => { 130 | it('should return true if attested by specified user', async () => { 131 | const att = await stow.getAttestation(testDataHash, provider); 132 | assert.isTrue(att); 133 | }); 134 | it('should return false if not attested by specified user', async () => { 135 | const att = await stow.getAttestation(testDataHash, user); 136 | assert.isFalse(att); 137 | }); 138 | }); 139 | describe('sign record', () => { 140 | it('should sign the record', async () => { 141 | const ethParams = { from: provider2, gas: 500000, gasPrice: 20000000000 }; 142 | const att = await stow.signRecord(testDataHash, ethParams); 143 | assert.equal(att.attester, provider2); 144 | assert.equal(att.dataHash, testDataHash); 145 | }); 146 | it('should fail when sign without a from user', async () => { 147 | const ethParams = { gas: 500000, gasPrice: 20000000000 }; 148 | try { 149 | await stow.signRecord(testDataHash, ethParams); 150 | } catch (e) { 151 | assert.equal(e.message, 'ethParams object does not contain a "from" key'); 152 | } 153 | }); 154 | it('should fail when sign with a user that is not registered in Stow', async () => { 155 | const ethParams = { from: '0xb717d7adf0d17f5f48bb7ff0035e30fcd19eed72', gas: 500000, gasPrice: 20000000000 }; 156 | try { 157 | await stow.signRecord(testDataHash, ethParams); 158 | } catch (e) { 159 | assert.equal(e.message, 'the address is not registered in Stow'); 160 | } 161 | }); 162 | it('should fail when sign with a user with no provenance', async () => { 163 | const ethParams = { from: user, gas: 500000, gasPrice: 20000000000 }; 164 | try { 165 | await stow.signRecord(testDataHash, ethParams); 166 | } catch (e) { 167 | assert.equal(e.message, 'The attestor does not have provenance (Invalid Attester)'); 168 | } 169 | }); 170 | it('should fail when sign a record that does not exists', async () => { 171 | const ethParams = { from: provider, gas: 500000, gasPrice: 20000000000 }; 172 | try { 173 | await stow.signRecord('Invalid Datahash', ethParams); 174 | } catch (e) { 175 | assert.equal(e.message, 'The record does not exists'); 176 | } 177 | }); 178 | it('should fail when sign with an attester that already sign that record', async () => { 179 | const ethParams = { from: provider2, gas: 500000, gasPrice: 20000000000 }; 180 | try { 181 | await stow.signRecord(testDataHash, ethParams); 182 | } catch (e) { 183 | assert.equal(e.message, 'The attestor have already signed this record'); 184 | } 185 | }); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /test/4-linnia-permissions-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import Web3 from 'web3'; 3 | import StowDeploy from './deployForTests'; 4 | 5 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 6 | const testDataHash = '0x276bc9ec8730ad53e827c0467c00473a53337e2cb4b61ada24760a217fb1ef14'; 7 | const testDataUri = 'QmbcfaK3bdpFTATifqXXLux4qx6CmgBUcd3fVMWxVszazP'; 8 | const testMetaData = 'Blood_Pressure'; 9 | const testSharedUri = '0xde1f76340a34698d41d362010bbc3c05c26f25d659904ef08ef7bd5eac0dbea4'; 10 | 11 | describe('Stow-permissions', async () => { 12 | const [admin, user1, user2, user3, provider] = await web3.eth.getAccounts(); 13 | let stow; 14 | let contracts; 15 | beforeEach('deploy the contracts and set up roles', async () => { 16 | stow = await StowDeploy.deploy(web3, { 17 | from: admin, 18 | gas: 4000000, 19 | }); 20 | contracts = await stow.getContractInstances(); 21 | await contracts.users.register({ from: user1 }); 22 | await contracts.users.register({ from: user2 }); 23 | await contracts.users.register({ from: provider }); 24 | await contracts.users.setProvenance(provider, 1, { from: admin }); 25 | // provider appends a file for user1 26 | await contracts.records.addRecordByProvider( 27 | testDataHash, 28 | user1, 29 | testMetaData, 30 | testDataUri, 31 | { 32 | from: provider, 33 | gas: 500000, 34 | }, 35 | ); 36 | // user1 shares the file with user2 37 | await contracts.permissions.grantAccess( 38 | testDataHash, 39 | user2, 40 | testSharedUri, 41 | { 42 | from: user1, 43 | gas: 500000, 44 | }, 45 | ); 46 | }); 47 | describe('view permission', () => { 48 | it('should return the permission info if viewer has access', async () => { 49 | const perm = await stow.getPermission(testDataHash, user2); 50 | assert.isTrue(perm.canAccess); 51 | assert.equal(perm.dataUri, testSharedUri); 52 | }); 53 | it( 54 | 'should return the permission info if viewer does not have access', 55 | async () => { 56 | const perm = await stow.getPermission(testDataHash, user3); 57 | assert.isFalse(perm.canAccess); 58 | assert.isEmpty(perm.dataUri); 59 | }, 60 | ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/5-linnia-record-class-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import Web3 from 'web3'; 3 | import Stow from '../src'; 4 | import StowDeploy from './deployForTests'; 5 | 6 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 7 | const testData = 'foobar'; 8 | const testDataHash = '0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e'; 9 | const testDataUri = 'QmYvPnLBAwfsWoj8NqGCt8ZoNDrz4mgVZT5zxNKNRHA9XK'; 10 | const testMetaData = 'Blood_Pressure'; 11 | const testSharedUri = 'QmbcfaK3bdpFTATifqXXLux4qx6CmgBUcd3fVMWxVszazP'; 12 | const privKey = 'wFGgG/Bv/36liIhdOGqH0TY5QpUVYkQP+Sdcbr1NgOI='; 13 | const pubKey = 'hQYhHJpzZH/tGhz1wtqSjkL17tJSnEEC4yVGyNTHNQY='; 14 | 15 | describe('Record class', async () => { 16 | const [admin, user1, user2, user3, provider] = await web3.eth.getAccounts(); 17 | let stow; 18 | let contracts; 19 | beforeEach('deploy the contracts and set up roles', async () => { 20 | stow = await StowDeploy.deploy(web3, { 21 | from: admin, 22 | gas: 4000000, 23 | }); 24 | contracts = await stow.getContractInstances(); 25 | await contracts.users.register({ from: user1 }); 26 | await contracts.users.register({ from: user2 }); 27 | await contracts.users.register({ from: provider }); 28 | await contracts.users.setProvenance(provider, 1, { from: admin }); 29 | // append a signed file 30 | await contracts.records.addRecordByProvider( 31 | testDataHash, 32 | user1, 33 | testMetaData, 34 | testDataUri, 35 | { 36 | from: provider, 37 | gas: 500000, 38 | }, 39 | ); 40 | // share the file with user2 41 | await contracts.permissions.grantAccess( 42 | testDataHash, 43 | user2, 44 | testSharedUri, 45 | { 46 | from: user1, 47 | gas: 500000, 48 | }, 49 | ); 50 | }); 51 | describe('get attestation', () => { 52 | it('should return true if attested by specified user', async () => { 53 | const record = await stow.getRecord(testDataHash); 54 | const at = await record.getAttestation(provider); 55 | assert.isTrue(at); 56 | }); 57 | it('should return false if not attested by specified user', async () => { 58 | const record = await stow.getRecord(testDataHash); 59 | const at = await record.getAttestation(user2); 60 | assert.isFalse(at); 61 | }); 62 | }); 63 | describe('get permission', () => { 64 | it('should return true if viewer has permission', async () => { 65 | const record = await stow.getRecord(testDataHash); 66 | const perm = await record.getPermission(user2); 67 | assert.isTrue(perm.canAccess); 68 | assert.equal(perm.dataUri, testSharedUri); 69 | }); 70 | it('should return false if viewer does not have permission', async () => { 71 | const record = await stow.getRecord(testDataHash); 72 | const perm = await record.getPermission(user3); 73 | assert.isFalse(perm.canAccess); 74 | assert.isEmpty(perm.dataUri); 75 | }); 76 | }); 77 | describe('decrypt', () => { 78 | it('should decrypt the data if hash is correct', async () => { 79 | // make the URI resolver always return the encrypted data 80 | const uriResolver = async (dataUri) => { 81 | // check that the URI being passed in is correct 82 | assert.equal(dataUri, testDataUri); 83 | return Stow.util.encrypt(pubKey, testData); 84 | }; 85 | const record = await stow.getRecord(testDataHash); 86 | const plain = await record.decryptData(privKey, uriResolver); 87 | assert.equal(plain.toString(), testData); 88 | }); 89 | it('should throw if hash does not match', async () => { 90 | // make the URI resolver return a decryptable but wrong data 91 | const uriResolver = async () => Stow.util.encrypt(pubKey, 'fox'); 92 | const record = await stow.getRecord(testDataHash); 93 | try { 94 | await record.decryptData(privKey, uriResolver); 95 | assert.fail('expected hash mismatch error not received'); 96 | } catch (error) { 97 | assert.equal(error.message, 'plaintext data hash mismatch'); 98 | } 99 | }); 100 | }); 101 | describe('decrypt permissioned', () => { 102 | it( 103 | 'should decrypt the data if has permission and hash is correct', 104 | async () => { 105 | const uriResolver = async (dataUri) => { 106 | // the URI being passed in should be the shared copy 107 | assert.equal(dataUri, testSharedUri); 108 | return Stow.util.encrypt(pubKey, testData); 109 | }; 110 | const record = await stow.getRecord(testDataHash); 111 | const plain = await record.decryptPermissioned( 112 | user2, 113 | privKey, 114 | uriResolver, 115 | ); 116 | assert.equal(plain.toString(), testData); 117 | }, 118 | ); 119 | it('should throw if viewer has no permission', async () => { 120 | const uriResolver = async () => Stow.util.encrypt(pubKey, testData); 121 | const record = await stow.getRecord(testDataHash); 122 | try { 123 | await record.decryptPermissioned(user3, privKey, uriResolver); 124 | assert.fail('expected permission error not received'); 125 | } catch (error) { 126 | assert.equal( 127 | error.message, 128 | 'viewer has no permission to view the data', 129 | ); 130 | } 131 | }); 132 | it('should throw if hash does not match', async () => { 133 | // make the URI resolver return a decryptable but wrong data 134 | const uriResolver = async () => Stow.util.encrypt(pubKey, 'fox'); 135 | const record = await stow.getRecord(testDataHash); 136 | try { 137 | await record.decryptPermissioned(user2, privKey, uriResolver); 138 | assert.fail('expected hash mismatch error not received'); 139 | } catch (error) { 140 | assert.equal(error.message, 'plaintext data hash mismatch'); 141 | } 142 | }); 143 | }); 144 | describe('verify data', () => { 145 | it('should return true if data hash matches', async () => { 146 | const record = await stow.getRecord(testDataHash); 147 | const verify = record.verifyData('foobar'); 148 | assert.isTrue(verify); 149 | }); 150 | it('should return true if data hash does not match', async () => { 151 | const record = await stow.getRecord(testDataHash); 152 | const verify = record.verifyData('fox'); 153 | assert.isFalse(verify); 154 | }); 155 | }); 156 | describe('reencrypt data', () => { 157 | it('should re-encrypt to the public key', async () => { 158 | const uriResolver = async (dataUri) => { 159 | assert.equal(dataUri, testDataUri); 160 | return Stow.util.encrypt(pubKey, testData); 161 | }; 162 | // make a new keypair 163 | const newKeys = Stow.util.genKeyPair(); 164 | const priv = newKeys.privateKey; 165 | const pub = newKeys.publicKey; 166 | const record = await stow.getRecord(testDataHash); 167 | const reencrypted = await record.reencryptData(pub, privKey, uriResolver); 168 | // the re-encrypted data should be decryptable by the new priv key 169 | const decrypted = Stow.util.decrypt(priv, reencrypted); 170 | assert.equal(decrypted.toString(), testData); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /test/deployForTests.js: -------------------------------------------------------------------------------- 1 | import TruffleContract from 'truffle-contract'; 2 | import StowHub from '@stowprotocol/stow-smart-contracts/build/contracts/StowHub.json'; 3 | import StowUsers from '@stowprotocol/stow-smart-contracts/build/contracts/StowUsers.json'; 4 | import StowRecords from '@stowprotocol/stow-smart-contracts/build/contracts/StowRecords.json'; 5 | import StowPermissions from '@stowprotocol/stow-smart-contracts/build/contracts/StowPermissions.json'; 6 | import MockToken from '@stowprotocol/stow-smart-contracts/build/contracts/ERC20Mock.json'; 7 | 8 | import Stow from '../src'; 9 | 10 | import _util from '../src/util'; 11 | 12 | const testDeploy = async (web3, opt) => { 13 | if (web3 === undefined) { 14 | throw Error('web3 is undefined!'); 15 | } 16 | 17 | const hubTemp = TruffleContract(StowHub); 18 | const usersTemp = TruffleContract(StowUsers); 19 | const recordsTemp = TruffleContract(StowRecords); 20 | const permissionsTemp = TruffleContract(StowPermissions); 21 | const mockTokenTemp = TruffleContract(MockToken); 22 | 23 | hubTemp.setProvider(web3.currentProvider); 24 | usersTemp.setProvider(web3.currentProvider); 25 | recordsTemp.setProvider(web3.currentProvider); 26 | permissionsTemp.setProvider(web3.currentProvider); 27 | mockTokenTemp.setProvider(web3.currentProvider); 28 | 29 | const hub = _util.truffleHack(hubTemp); 30 | const users = _util.truffleHack(usersTemp); 31 | const records = _util.truffleHack(recordsTemp); 32 | const permissions = _util.truffleHack(permissionsTemp); 33 | const mockToken = _util.truffleHack(mockTokenTemp); 34 | 35 | // deploy the hub 36 | const hubInstance = await hub.new(opt); 37 | // deploy Users 38 | const usersInstance = await users.new(hubInstance.address, opt); 39 | // deply records 40 | const recordsInstance = await records.new(hubInstance.address, opt); 41 | // deploy permissions 42 | const permissionsInstace = await permissions.new(hubInstance.address, opt); 43 | // set all addresses in hub 44 | await hubInstance.setUsersContract(usersInstance.address, opt); 45 | await hubInstance.setRecordsContract(recordsInstance.address, opt); 46 | await hubInstance.setPermissionsContract(permissionsInstace.address, opt); 47 | 48 | // deploy mockToken 49 | const tokenInstance = await mockToken.new(opt); 50 | await tokenInstance.unpause(opt); 51 | await tokenInstance.transfer(recordsInstance.address, web3.utils.toWei('1000', 'finney'), opt); 52 | return { 53 | hubInstance, 54 | usersInstance, 55 | recordsInstance, 56 | permissionsInstace, 57 | tokenInstance, 58 | }; 59 | }; 60 | 61 | /** 62 | - * Deploy Stow contracts, and construct the Stow API that uses the newly 63 | - * deployed contracts. 64 | - * @param {Object} web3 An instantiated web3 API object, configured to the 65 | - * network you want to deploy the contracts on 66 | - * @param {?Object} opt Optional web3 transaction object 67 | - * @returns {Promise} A Stow API object using the deployed contracts 68 | */ 69 | 70 | class StowDeploy { 71 | static async deploy(web3, opt = {}) { 72 | const deployed = await testDeploy(web3, opt); 73 | return new Stow(web3, { 74 | hubAddress: deployed.hubInstance.address, 75 | tokenAddress: deployed.tokenInstance.address, 76 | }); 77 | } 78 | } 79 | 80 | export default StowDeploy; 81 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const yargs = require('yargs'); 4 | 5 | const libraryName = 'Stow'; 6 | const plugins = []; 7 | 8 | let outputFile; 9 | 10 | if (yargs.argv.p) { 11 | plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true })); 12 | outputFile = libraryName + '.min.js'; 13 | } else { 14 | outputFile = libraryName + '.js'; 15 | } 16 | 17 | const entry = { 18 | app: [__dirname + '/src/index.js'] 19 | }; 20 | 21 | const output = { 22 | path: path.join(__dirname, '/dist'), 23 | filename: 'index.js', 24 | library: 'Stow', 25 | libraryTarget: 'umd', 26 | libraryExport: "default", 27 | umdNamedDefine: true 28 | }; 29 | 30 | const devtool = 'source-map'; 31 | 32 | const config = [{ 33 | entry: entry, 34 | devtool: devtool, 35 | mode: 'production', 36 | performance: { hints: false }, 37 | output: output, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.js$/, 42 | exclude: /node_modules/, 43 | use: [ 44 | { 45 | loader: 'babel-loader', 46 | options: { 47 | "presets": ["env"] 48 | } 49 | }, 50 | ] 51 | }, 52 | ] 53 | }, 54 | resolve: { 55 | extensions: ['.js'], 56 | modules: [ 57 | 'node_modules', 58 | 'src', 59 | ] 60 | }, 61 | plugins: plugins, 62 | }]; 63 | 64 | module.exports = config; 65 | --------------------------------------------------------------------------------