├── .babelrc ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── iden3-basic-usage.example.js └── sparse-merkle-tree.example.js ├── flow.md ├── index.js ├── package.json ├── src ├── admin │ └── requests.js ├── api-client │ ├── discovery.js │ ├── discovery.test.js │ ├── http-debug.js │ ├── name-resolver.js │ ├── name-resolver.test.js │ ├── name-server.js │ ├── notification-server.js │ ├── private-folder.js │ └── relay.js ├── auth │ ├── auth.js │ └── dapp.js ├── canary.test.js ├── claim │ ├── claim-assign-name.js │ ├── claim-assign-name.test.js │ ├── claim-authorize-eth-key.js │ ├── claim-authorize-eth-key.test.js │ ├── claim-authorize-ksign-babyjub.js │ ├── claim-authorize-ksign-babyjub.test.js │ ├── claim-authorize-ksign-secp256k1.js │ ├── claim-authorize-ksign-secp256k1.test.js │ ├── claim-basic.js │ ├── claim-basic.test.js │ ├── claim-link-object-identity.js │ ├── claim-link-object-identity.test.js │ ├── claim-set-root-key.js │ ├── claim-set-root-key.test.js │ ├── claim-utils.js │ ├── claim-utils.test.js │ ├── claim.js │ ├── entry.js │ └── entry.test.js ├── constants.js ├── crypto │ ├── babyjub-utils.js │ ├── babyjub-utils.test.js │ ├── crypto.js │ ├── eddsa-babyjub.js │ ├── eddsa-babyjub.test.js │ ├── mimc7.js │ └── poseidon.js ├── db │ ├── db.js │ ├── localstorage-db.js │ ├── localstorage-db.test.js │ ├── memory-db.js │ └── memory-db.test.js ├── eth │ ├── counterfactual.js │ ├── counterfactual.test.js │ └── testbytecode.json ├── id │ ├── id-utils.js │ ├── id-utils.test.js │ ├── id.js │ └── id.test.js ├── identity │ ├── identity-utils.js │ ├── identity-utils.test.js │ ├── identity.js │ └── identity.test.js ├── index.js ├── key-container │ ├── kc-utils.js │ ├── key-container.js │ └── key-container.test.js ├── manager │ └── manager-notification.js ├── protocols │ ├── README.md │ ├── login.js │ ├── login.test.js │ ├── login_flow.png │ ├── login_overview.png │ ├── login_spec.md │ ├── login_spec_rationale.md │ ├── nonceDB.js │ ├── nonceDB.test.js │ ├── proofs.js │ ├── proofs.test.js │ └── protocols.js ├── sparse-merkle-tree │ ├── sparse-merkle-tree-utils.js │ ├── sparse-merkle-tree-utils.test.js │ ├── sparse-merkle-tree.js │ └── sparse-merkle-tree.test.js ├── utils.js └── utils.test.js └── test ├── id-private-folder.test.js ├── id.test.js ├── notification-server.test.js ├── private-folder.test.js ├── protocols-login.test.js └── recv-notifications.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "flow"], 3 | "sourceMaps": true, 4 | "retainLines": true, 5 | "plugins": [ 6 | [ "transform-runtime", 7 | { "polyfill": false, 8 | "regenerator": true 9 | } 10 | ], 11 | "syntax-flow", 12 | ] 13 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "airbnb-base", 5 | "plugin:flowtype/recommended" 6 | ], 7 | "env": { 8 | "browser": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | "plugins": [ 13 | "mocha", 14 | "flowtype" 15 | ], 16 | "rules": { 17 | "mocha/no-exclusive-tests": "error", 18 | "max-len": ["error", { "code": 140, "comments": 200, "ignoreStrings": true, "ignoreTemplateLiterals": true }], 19 | "no-unused-vars": [2, { "varsIgnorePattern": "export^" }], 20 | "no-return-assign": [0], 21 | "no-underscore-dangle": [0], 22 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 23 | "func-names": [0], 24 | "class-methods-use-this": [0], 25 | "no-bitwise": [0], 26 | "no-param-reassign": "off", 27 | "no-console": [2, { "allow": ["warn", "error"] }], 28 | "import/prefer-default-export": [0], 29 | "lines-between-class-members": [ "error", "always", { "exceptAfterSingleLine": true }] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.*/test/.* 3 | 4 | [include] 5 | ./node_modules/events 6 | 7 | [libs] 8 | 9 | [lints] 10 | 11 | [options] 12 | 13 | [strict] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | notes.js 4 | tmp 5 | .idea 6 | .vscode 7 | iden3js-bundle.js 8 | lib 9 | flow-typed 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - "10" 6 | install: 7 | - npm install || true 8 | - rm -rf /home/travis/build/iden3/iden3js/node_modules/web3/node_modules/websocket/.git/ 9 | - npm install 10 | script: 11 | - npm run flow check 12 | - npm run lint 13 | - npm run test:unit 14 | -------------------------------------------------------------------------------- /examples/iden3-basic-usage.example.js: -------------------------------------------------------------------------------- 1 | // import iden3js 2 | const iden3 = require('../index'); 3 | 4 | // new database 5 | const db = new iden3.Db(); 6 | // new key container using localStorage 7 | const keyContainer = new iden3.KeyContainer(db); 8 | // unlock the KeyContainer for the next 30 seconds 9 | const passphrase = 'pass'; 10 | keyContainer.unlock(passphrase); 11 | 12 | // generate master seed 13 | const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye'; 14 | keyContainer.setMasterSeed(mnemonic); 15 | 16 | // Generate keys for first identity 17 | const keys = keyContainer.createKeys(); 18 | 19 | /* 20 | keys: [ 21 | '0xc7d89fe96acdb257b434bf580b8e6eb677d445a9', 22 | '0x03c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff57833', 23 | '0xf3c9f94e4eaffef676d4fd3b4fc2732044caea91', 24 | '0xb07079bd6238fa845dc77bbce3ec2edf98ffe735' 25 | ]; 26 | */ 27 | // It should be noted that 'keys' are in form of ethereum addresses except 28 | // key[1] that is a pubic key in its compressed form 29 | const keyAddressOp = keys[0]; 30 | const keyPublicOp = keys[1]; 31 | const keyRecover = keys[2]; 32 | const keyRevoke = keys[3]; 33 | 34 | // For more info and details about mnemonic, see section Usage > KeyContainer 35 | 36 | // create a new relay object 37 | const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 38 | const relayUrl = 'http://127.0.0.1:8000/api/unstable'; 39 | const relay = new iden3.Relay(relayUrl); 40 | 41 | // create a new id object 42 | const id = new iden3.Id(keyPublicOp, keyRecover, keyRevoke, relay, relayAddr, '', undefined, 0); 43 | 44 | // generates the counterfactoual contract through the relay, get the identity address as response 45 | let proofKsign = {}; 46 | 47 | // console.log('Create Identity'); 48 | id.createID() 49 | .then((createIdRes) => { 50 | // Successfull create identity api call to relay 51 | console.log(createIdRes.idAddr); // Identity counterfactoual address 52 | proofKsign = createIdRes.proofClaim; 53 | console.log(proofKsign); // Proof of claim regarding authorization of key public operational 54 | 55 | console.log('Create and authorize new key for address'); 56 | // generate new key from identity and issue a claim to relay in order to authorize new key 57 | const keyLabel = 'testKey'; 58 | const newKey = id.createKey(keyContainer, keyLabel, true); 59 | id.authorizeKSignSecp256k1(keyContainer, id.keyOperationalPub, newKey) 60 | .then((authRes) => { 61 | proofKsign = authRes.data.proofClaim; 62 | console.log(proofKsign); 63 | }) 64 | .catch((error) => { 65 | console.error(error.message); 66 | }); 67 | 68 | // console.log('Bind label to an identity'); 69 | // bind the identity address to a label. It send required data to name-resolver service and name-resolver issue a claim 'assignName' binding identity address with label 70 | const name = 'testName'; 71 | id.bindId(keyContainer, name) 72 | .then((bindRes) => { 73 | console.log(bindRes.data); 74 | // request idenity address to name-resolver ( currently name-resolver service is inside relay) from a given label 75 | relay.resolveName(`${name}@iden3.io`) 76 | .then((resolveRes) => { 77 | const idAddress = resolveRes.data.idAddr; 78 | console.log(`${name}@iden3.io associated with addres: ${idAddress}`); 79 | }) 80 | .catch((error) => { 81 | console.error(error.message); 82 | }); 83 | }) 84 | .catch((error) => { 85 | console.error(error.message); 86 | }); 87 | }) 88 | .catch((error) => { 89 | console.error(error.message); 90 | }); 91 | -------------------------------------------------------------------------------- /examples/sparse-merkle-tree.example.js: -------------------------------------------------------------------------------- 1 | const snarkjs = require('snarkjs'); 2 | const iden3 = require('../index'); 3 | const helpers = require('../src/sparse-merkle-tree/sparse-merkle-tree-utils'); 4 | 5 | const { bigInt } = snarkjs; 6 | 7 | // New database 8 | const db = new iden3.Db(); 9 | // Hardcoded id address for multi identity purposes 10 | const idAddr = '0xq5soghj264eax651ghq1651485ccaxas98461251d5f1sdf6c51c5d1c6sd1c651'; 11 | // New merkle tree class instance 12 | console.log('Create Merkle Tree'); 13 | const mt = new iden3.sparseMerkleTree.SparseMerkleTree(db, idAddr); 14 | 15 | // Add leaf 16 | console.log('Add leaf'); 17 | // Create data leaf structure 18 | const leaf = [bigInt(12), bigInt(45), bigInt(78), bigInt(41)]; 19 | // Add leaf to the merkle tree 20 | mt.addClaim(leaf); 21 | 22 | // Get leaf data by hash Index 23 | console.log('Get leaf data'); 24 | // Retrieve data of the leaf 25 | const leafData = mt.getClaimByHi(leaf.slice(2)); 26 | 27 | // Generate Proof 28 | console.log('Generate proof'); 29 | // Get leafProof for a given leaf index 30 | const leafProof = mt.generateProof(leaf.slice(2)); 31 | // Code `leafProof` into a hexadecimal string 32 | const leafProofHex = iden3.utils.bytesToHex(leafProof); 33 | 34 | // CheckProof 35 | console.log('Check proof'); 36 | // Proof-of-existence 37 | console.log('Proof-of-existence'); 38 | // Retrieve merkle tree root and code it into a string 39 | const rootHex = iden3.utils.bytesToHex(mt.root); 40 | // Code hash index into a hexadecimal string 41 | // Compute total hash of the leaf and code it into an hexadecimal string 42 | const hashes = iden3.sparseMerkleTree.getHiHv(leaf); 43 | const hiHex = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes[0])); 44 | const hvHex = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes[1])); 45 | // Check if a leaf is on the merkle tree 46 | const verified = iden3.sparseMerkleTree.checkProof(rootHex, leafProofHex, hiHex, hvHex); 47 | console.log(verified); 48 | 49 | // CheckProof 50 | console.log('Check proof'); 51 | // Proof-of-non-existence 52 | console.log('Proof-of-non-existence'); 53 | // create leaf2 data structure 54 | const leaf2 = [bigInt(1), bigInt(2), bigInt(3), bigInt(4)]; 55 | // Code hash index into a hexadecimal string 56 | // Compute total hash of the leaf and code it into an hexadecimal string 57 | const hashes2 = iden3.sparseMerkleTree.getHiHv(leaf2); 58 | const hiHex2 = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes2[0])); 59 | const hvHex2 = iden3.utils.bytesToHex(helpers.bigIntToBuffer(hashes2[1])); 60 | // Get leafProof for a given leaf index 61 | const leafProof2 = mt.generateProof(leaf2.slice(2)); 62 | // Code `leafProof` into a hexadecimal string 63 | const leafProofHex2 = iden3.utils.bytesToHex(leafProof2); 64 | // Check if a leaf is on the merkle tree 65 | const verified2 = iden3.sparseMerkleTree.checkProof(rootHex, leafProofHex2, hiHex2, hvHex2); 66 | console.log(verified2); 67 | -------------------------------------------------------------------------------- /flow.md: -------------------------------------------------------------------------------- 1 | With yarn: 2 | ``` 3 | yarn add --dev babel-cli babel-preset-flow flow-bin 4 | ``` 5 | 6 | With npm: 7 | ``` 8 | npm install --save-dev babel-cli babel-preset-flow flow-bin babel-eslint eslint-plugin-flowtype babel-preset-env 9 | ``` 10 | 11 | Or better, install the npm dependencies from the package.json: 12 | ``` 13 | npm install 14 | ``` 15 | 16 | Plugin for VSCode: https://github.com/flowtype/flow-for-vscode 17 | 18 | Manually via terminal: 19 | ``` 20 | yarn run flow 21 | ``` 22 | 23 | To add flow to mocha tests, you need to add this line: 24 | ``` 25 | import { describe, it } from 'mocha'; 26 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const iden3js = require('./src/index.js'); 2 | 3 | module.exports = iden3js; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iden3/iden3", 3 | "version": "0.0.26", 4 | "engines": { 5 | "node": "^8" 6 | }, 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "description": "", 11 | "main": "index.js", 12 | "scripts": { 13 | "test:unit": "rm -rf tmp && mocha --require babel-core/register --require mock-local-storage --recursive 'src/**/*.test.js' --timeout 100000", 14 | "test:unitf": "rm -rf tmp && mocha --require babel-core/register --require mock-local-storage --bail -ig 'pow' --recursive 'src/**/*.test.js' --timeout 100000 # FAST", 15 | "test:int": "rm -rf tmp && mocha --require babel-core/register --require mock-local-storage --timeout 20000", 16 | "build-test:int": "npm run build && npm run test:int", 17 | "test:all": "npm run test:unit && npm run test:int", 18 | "build": "babel --ignore '**/*.test.js' src/ -d lib/", 19 | "flow": "flow", 20 | "browserify": "npm run build && browserify lib/index.js --standalone iden3 > iden3js-bundle.js", 21 | "copy-prepublish": "cp package.json README.md LICENSE lib", 22 | "build-publish": "npm run build && npm run copy-prepublish && npm publish lib", 23 | "lint": "eslint ./src" 24 | }, 25 | "author": "iden3.io", 26 | "license": "ISC", 27 | "dependencies": { 28 | "axios": "^0.18.0", 29 | "bigint-buffer": "^1.1.2", 30 | "bip39": "^2.5.0", 31 | "bs58": "^4.0.1", 32 | "blake-hash": "^1.1.0", 33 | "chai": "^4.1.2", 34 | "circomlib": "0.0.18", 35 | "ethereumjs-util": "^5.2.0", 36 | "ethereumjs-wallet": "^0.6.2", 37 | "ethjs-util": "^0.1.6", 38 | "hdkey": "^1.1.1", 39 | "keccak": "^1.4.0", 40 | "keythereum": "^1.0.4", 41 | "mocha": "^5.2.0", 42 | "pbkdf2-sha256": "^1.1.1", 43 | "qrcode-generator": "^1.4.3", 44 | "snarkjs": "^0.1.13", 45 | "tweetnacl": "^1.0.1", 46 | "tweetnacl-sealedbox-js": "^1.1.0", 47 | "tweetnacl-util": "^0.15.0", 48 | "web3": "1.0.0-beta.33", 49 | "ws": "^5.2.2", 50 | "babel-runtime": "6.26.0" 51 | }, 52 | "devDependencies": { 53 | "@babel/core": "^7.3.3", 54 | "@babel/plugin-transform-flow-strip-types": "^7.2.3", 55 | "babel-cli": "^6.26.0", 56 | "babel-eslint": "^10.0.1", 57 | "babel-plugin-syntax-flow": "^6.18.0", 58 | "babel-plugin-transform-class-properties": "^6.24.1", 59 | "babel-plugin-transform-runtime": "^6.23.0", 60 | "babel-preset-env": "^1.7.0", 61 | "babel-preset-flow": "^6.23.0", 62 | "browserify": "^16.2.3", 63 | "bufferutil": "^4.0.1", 64 | "eslint": "^5.14.1", 65 | "eslint-config-airbnb-base": "^13.1.0", 66 | "eslint-plugin-flowtype": "^3.4.2", 67 | "eslint-plugin-import": "^2.16.0", 68 | "eslint-plugin-mocha": "^5.3.0", 69 | "flow-bin": "^0.92.0", 70 | "flow-remove-types": "^1.2.3", 71 | "flow-typed": "^2.5.1", 72 | "mock-local-storage": "^1.1.8", 73 | "node-localstorage": "^1.3.1", 74 | "utf-8-validate": "^5.0.2" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/admin/requests.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const fs = require('fs'); 3 | 4 | const adminurl = 'http://127.0.0.1:8001'; 5 | 6 | /* eslint no-console: "off" */ 7 | 8 | /** 9 | * Function called from the admin cli, to get the Relay Admin API info 10 | */ 11 | function info() { 12 | axios.get(`${adminurl}/info`).then((res) => { 13 | console.log(res.data); 14 | }); 15 | } 16 | 17 | /** 18 | * Function called from the admin cli, to get the Relay Admin API rawdump 19 | * if filepath is not specified, prints the data 20 | * @param {String} filepath - file path to store the file with the raw dump data 21 | */ 22 | function rawdump(filepath) { 23 | axios.get(`${adminurl}/rawdump`).then((res) => { 24 | console.log(res.data); 25 | if (filepath) { 26 | fs.writeFile(filepath, JSON.stringify(res.data), 'utf8', () => {}); 27 | } else { 28 | console.log(res.data); 29 | } 30 | }); 31 | } 32 | 33 | /** 34 | * Function called from the admin cli, to import the exported rawdump to the Relay Admin API 35 | * @param {String} filepath - file path from where to get the raw dump data to import 36 | */ 37 | function rawimport(filepath) { 38 | fs.readFile(filepath, 'utf8', (err, data) => { 39 | console.log('a', JSON.parse(data)); 40 | axios.post(`${adminurl}/rawimport`, JSON.parse(data)).then((res) => { 41 | console.log(res.data); 42 | }); 43 | }); 44 | } 45 | 46 | /** 47 | * Function called from the admin cli, to get the Relay Admin API claimsdump 48 | */ 49 | function claimsdump() { 50 | axios.get(`${adminurl}/claimsdump`).then((res) => { 51 | console.log(res.data); 52 | }); 53 | } 54 | 55 | /** 56 | * Function called from the admin cli, to get the Relay Admin API mimc7 57 | */ 58 | function mimc7(elements) { 59 | axios.post(`${adminurl}/mimc7`, elements).then((res) => { 60 | console.log(res.data); 61 | }); 62 | } 63 | 64 | /** 65 | * Function called from the admin cli, to get the Relay Admin API addGenericClaim 66 | */ 67 | function addGenericClaim(indexData, data) { 68 | const d = { indexData, data }; 69 | axios.post(`${adminurl}/genericClaim`, d).then((res) => { 70 | console.log(res.data); 71 | }); 72 | } 73 | 74 | module.exports = { 75 | info, 76 | rawdump, 77 | rawimport, 78 | claimsdump, 79 | mimc7, 80 | addGenericClaim, 81 | }; 82 | -------------------------------------------------------------------------------- /src/api-client/discovery.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Entity = { 4 | idAddr: string, 5 | name: string, 6 | kOpPub: string, 7 | trusted: { relay: boolean }, 8 | }; 9 | 10 | /** 11 | * Class representing a discovery client 12 | */ 13 | class Discovery { 14 | entities: { [string]: Entity }; 15 | 16 | /** 17 | * Initialization Discovery object 18 | * @param {String} entitiesJSON - map of entitites serialized in JSON 19 | */ 20 | constructor(entitiesJSON: string) { 21 | this.entities = JSON.parse(entitiesJSON); 22 | Object.keys(this.entities).forEach((idAddr) => { this.entities[idAddr].idAddr = idAddr; }); 23 | } 24 | 25 | /** 26 | * Get entry from an idAddr 27 | * @param {String} idAddr 28 | * @returns {?Entity} entity 29 | */ 30 | getEntity(idAddr: string): ?Entity { 31 | return this.entities[idAddr]; 32 | } 33 | } 34 | 35 | /* Temporary entitites configuration data. Use only for testing. To be 36 | * deleted after the discovery protocol becomes stable. */ 37 | const testEntitiesJSON = ` 38 | { 39 | "0x0123456789abcdef0123456789abcdef01234567": { 40 | "name": "iden3-test-relay", 41 | "kOpAddr": "0xe0fbce58cfaa72812103f003adce3f284fe5fc7c", 42 | "kOpPub": "0x036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d59", 43 | "trusted": { "relay": true } 44 | }, 45 | "11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZoWij": { 46 | "name": "iden3-test-relay2", 47 | "kOpAddr": "0x7633bc9012f924100fae50d6dda7162b0bba720d", 48 | "kOpPub": "0x036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d59", 49 | "trusted": { "relay": true } 50 | }, 51 | "1N7d2qVEJeqnYAWVi5Cq6PLj6GwxaW6FYcfmY2fps": { 52 | "name": "iden3-test-relay3", 53 | "kOpPub": "117f0a278b32db7380b078cdb451b509a2ed591664d1bac464e8c35a90646796", 54 | "trusted": { "relay": true } 55 | }, 56 | "1pnWU7Jdr4yLxp1azs1r1PpvfErxKGRQdcLBZuq3Z": { 57 | "name": "iden3-name-server", 58 | "kOpAddr": "0x7633bc9012f924100fae50d6dda7162b0bba720d", 59 | "kOpPub": "0x036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d59", 60 | "trusted": { "relay": true } 61 | } 62 | }`; 63 | 64 | module.exports = { 65 | Discovery, 66 | testEntitiesJSON, 67 | }; 68 | -------------------------------------------------------------------------------- /src/api-client/discovery.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { describe, it } from 'mocha'; 4 | import { Discovery, testEntitiesJSON } from './discovery'; 5 | 6 | const chai = require('chai'); 7 | 8 | const { expect } = chai; 9 | 10 | const testEntityIdAddr = '0x0123456789abcdef0123456789abcdef01234567'; 11 | 12 | describe('[Discovery]', () => { 13 | it('new & getEntity', () => { 14 | const discovery = new Discovery(testEntitiesJSON); 15 | const testEntity = discovery.getEntity(testEntityIdAddr); 16 | expect(testEntity).to.be.not.equal(undefined); 17 | if (testEntity == null) { return; } 18 | expect(testEntity.idAddr).to.be.equal(testEntityIdAddr); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/api-client/http-debug.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const util = require('util'); 3 | 4 | function printResponse(r) { 5 | console.log(`${r.status} ${r.statusText}`); 6 | console.log(`headers: ${util.inspect(r.headers)}`); 7 | console.log(util.inspect(r.data)); 8 | } 9 | 10 | /* eslint no-console: "off" */ 11 | export function axiosGetDebug(url, config) { 12 | const h = () => { console.log(`### GET ${url}`); }; 13 | return axios.get(url, config).then((res) => { 14 | h(); 15 | console.log('Returns:\n```js'); 16 | console.log(JSON.stringify(res.data, null, 2)); 17 | console.log('```'); 18 | return res; 19 | }).catch((err) => { 20 | h(); 21 | console.log('Input headers:\n', util.inspect(err.config.headers)); 22 | console.log('Returns error:'); 23 | printResponse(err.response); 24 | throw err; 25 | }); 26 | } 27 | 28 | export function axiosPostDebug(url, data, config) { 29 | const h = () => { 30 | console.log(`### POST ${url}`); 31 | console.log('Input:\n```js'); 32 | console.log(JSON.stringify(data, null, 2)); 33 | console.log('```'); 34 | }; 35 | return axios.post(url, data, config).then((res) => { 36 | h(); 37 | console.log('Returns:\n```js'); 38 | console.log(JSON.stringify(res.data, null, 2)); 39 | console.log('```'); 40 | return res; 41 | }).catch((err) => { 42 | h(); 43 | console.log('Input headers:\n', util.inspect(err.config.headers)); 44 | console.log('Returns error:'); 45 | printResponse(err.response); 46 | throw err; 47 | }); 48 | } 49 | 50 | export function axiosDeleteDebug(url, data, config) { 51 | const h = () => { 52 | console.log(`### DELETE ${url}`); 53 | console.log('Input:\n```js'); 54 | console.log(JSON.stringify(data, null, 2)); 55 | console.log('```'); 56 | }; 57 | return axios.delete(url, data, config).then((res) => { 58 | h(); 59 | console.log('Returns:\n```js'); 60 | console.log(JSON.stringify(res.data, null, 2)); 61 | console.log('```'); 62 | return res; 63 | }).catch((err) => { 64 | h(); 65 | console.log('Input headers:\n', util.inspect(err.config.headers)); 66 | console.log('Returns error:'); 67 | printResponse(err.response); 68 | throw err; 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /src/api-client/name-resolver.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * Class representing a name resolver 5 | */ 6 | class NameResolver { 7 | names: { [string]: string }; 8 | 9 | /** 10 | * Initialization NameResolver object 11 | * @param {String} namesJSON - map of domains to id in JSON 12 | */ 13 | constructor(namesJSON: string) { 14 | this.names = JSON.parse(namesJSON); 15 | } 16 | 17 | /** 18 | * Resolve a name to an id 19 | * @param {String} name 20 | * @returns {?String} id 21 | */ 22 | resolve(name: string): ?string { 23 | return this.names[name]; 24 | } 25 | } 26 | 27 | /* Temporary names configuration data. Use only for testing. To be deleted 28 | * after the domain name assignment / resolution becomes stable. */ 29 | const testNamesJSON = ` 30 | { 31 | "iden3.eth": "1N7d2qVEJeqnYAWVi5Cq6PLj6GwxaW6FYcfmY2fps" 32 | }`; 33 | 34 | module.exports = { 35 | NameResolver, 36 | testNamesJSON, 37 | }; 38 | -------------------------------------------------------------------------------- /src/api-client/name-resolver.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { describe, it } from 'mocha'; 4 | import { NameResolver, testNamesJSON } from './name-resolver'; 5 | 6 | const chai = require('chai'); 7 | 8 | const { expect } = chai; 9 | 10 | const testName = 'iden3.eth'; 11 | const testEntityIdAddr = '1N7d2qVEJeqnYAWVi5Cq6PLj6GwxaW6FYcfmY2fps'; 12 | 13 | describe('[NameResolver]', () => { 14 | it('new & resolve', () => { 15 | const nameResolver = new NameResolver(testNamesJSON); 16 | const testIdAddr = nameResolver.resolve(testName); 17 | expect(testIdAddr).to.be.not.equal(undefined); 18 | if (testIdAddr == null) { return; } 19 | expect(testIdAddr).to.be.equal(testEntityIdAddr); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/api-client/name-server.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { AxiosPromise } from 'axios'; 3 | 4 | import { axiosGetDebug, axiosPostDebug } from './http-debug'; 5 | 6 | const axios = require('axios'); 7 | const login = require('../protocols/login'); 8 | const KeyContainer = require('../key-container/key-container'); 9 | const proofs = require('../protocols/proofs'); 10 | 11 | /** 12 | * Class representing a the NameServer 13 | * It contains all the relay API calls 14 | * @param {String} url 15 | */ 16 | class NameServer { 17 | url: string; 18 | debug: boolean; 19 | getFn: (string, any) => any; 20 | postFn: (string, any) => any; 21 | 22 | /** 23 | * Initialization name server object 24 | * @param {String} url - NameServer Url identifier 25 | */ 26 | constructor(url: string, debug: boolean = false) { 27 | this.url = url; 28 | this.debug = debug; 29 | if (this.debug) { 30 | this.getFn = axiosGetDebug; 31 | this.postFn = axiosPostDebug; 32 | } else { 33 | this.getFn = axios.get; 34 | this.postFn = axios.post; 35 | } 36 | } 37 | 38 | /** 39 | * Construct proper object to bind an identity adress to a label 40 | * Name resolver server creates the claim binding { Label - Identity address } 41 | * @param {Object} kc - Keycontainer 42 | * @param {String} ksing - ksign public key used to sign the request 43 | * @param {Object} proofKSign - AuthorizeKSignSecp256k1 ProofClaim of ksign 44 | * @param {String} id - Identity address 45 | * @param {String} name - Label to bind 46 | * @return {Object} Http response 47 | */ 48 | bindId(kc: KeyContainer, ksign: string, proofKSign: proofs.ProofClaim, 49 | id: string, name: string): AxiosPromise { 50 | const assignNameSignedReq = login.signGenericSigV01(kc, id, 51 | ksign, proofKSign, 600, { assignName: name }); 52 | return this.postFn(`${this.url}/names`, { jws: assignNameSignedReq }); 53 | } 54 | 55 | /** 56 | * Search name string into the name resolver and it retrieves the corresponding public address 57 | * @param {String} name - Label to search into the name resolver tree 58 | * @return {Object} Http response 59 | */ 60 | resolveName(name: string): AxiosPromise { 61 | return this.getFn(`${this.url}/names/${name}`); 62 | } 63 | } 64 | 65 | module.exports = NameServer; 66 | -------------------------------------------------------------------------------- /src/api-client/notification-server.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { AxiosPromise } from 'axios'; 3 | import { axiosGetDebug, axiosPostDebug, axiosDeleteDebug } from './http-debug'; 4 | // import { ProofClaim } from '../protocols/proofs'; 5 | const axios = require('axios'); 6 | const proofs = require('../protocols/proofs'); 7 | 8 | export const NOTIFCLAIMV01 = 'iden3.proofclaim.v0_1'; 9 | export const NOTIFTXTV01 = 'iden3.txt.v0_1'; 10 | 11 | type Notification = { 12 | type: string, 13 | data: Object, 14 | }; 15 | 16 | export function newNotifClaim(proofClaim: proofs.ProofClaim): Notification { 17 | return { 18 | type: NOTIFCLAIMV01, 19 | data: proofClaim, 20 | }; 21 | } 22 | 23 | export function newNotifTxt(txt: string): Notification { 24 | return { 25 | type: NOTIFTXTV01, 26 | data: txt, 27 | }; 28 | } 29 | 30 | /** 31 | * Class representing the notification server 32 | * It contains all the relay API calls 33 | */ 34 | export class NotificationServer { 35 | url: string; 36 | debug: boolean; 37 | getFn: (string, any) => any; 38 | postFn: (string, any, any) => any; 39 | deleteFn: (string, any) => any; 40 | 41 | /** 42 | * Initialization notification server object 43 | * @param {String} url - NameServer Url identifier 44 | * @param {Boolean} debug - Default to false. Prints Http response into the console if true 45 | */ 46 | constructor(url: string, debug: boolean = false) { 47 | this.url = url; 48 | this.debug = debug; 49 | if (this.debug) { 50 | this.getFn = axiosGetDebug; 51 | this.postFn = axiosPostDebug; 52 | this.deleteFn = axiosDeleteDebug; 53 | } else { 54 | this.getFn = axios.get; 55 | this.postFn = axios.post; 56 | this.deleteFn = axios.delete; 57 | } 58 | } 59 | 60 | /** 61 | * Request login into notification server 62 | * Login would be done according identity assert protocol 63 | * Server response would be signature request package 64 | * @return {Object} Http response 65 | */ 66 | requestLogin(): AxiosPromise { 67 | return this.getFn(`${this.url}/login`); 68 | } 69 | 70 | /** 71 | * Submit login into notification server 72 | * Login would be done according identity assert protocol 73 | * Server response would be a jws token 74 | * @param {String} signedPacket - Identity assertion packet represented into an encoded base64 string 75 | * @return {Object} Http response 76 | */ 77 | submitLogin(signedPacket: string): AxiosPromise { 78 | return this.postFn(`${this.url}/login`, { jws: signedPacket }); 79 | } 80 | 81 | /** 82 | * Set notification on server for an spcefic identity 83 | * @param {String} id - Identity address 84 | * @param {String} signedMsg - Signed Message to send as notification 85 | * @return {Object} Http response 86 | */ 87 | postNotification(id: string, signedMsg: string): AxiosPromise { 88 | return this.postFn(`${this.url}/notifications/${id}`, { jws: signedMsg }); 89 | } 90 | 91 | /** 92 | * Gets last 10 notifications available for an specific address 93 | * Request can be done with following parameters: 94 | * before identifier ['beforeId']: returns previous 10 notifications from the notification identifier 95 | * after identifier ['afterId']: returns next 10 notifications from the notification identifier 96 | * @param {String} token - Session token to be identified 97 | * @param {Number} beforeId - Retrieve notifications less than this identifier 98 | * @param {Number} afterId - Retrieve notifications greater than this identifier 99 | * @return {Object} Http response 100 | */ 101 | getNotifications(token: string, beforeId: number = 0, afterId: number = 0): AxiosPromise { 102 | // Handle http parameters 103 | let urlParams = ''; 104 | if (beforeId !== 0) { 105 | urlParams = `?beforeid=${beforeId.toString()}`; 106 | } else if (afterId !== 0) { 107 | urlParams = `?afterid=${afterId.toString()}`; 108 | } 109 | 110 | return this.getFn(`${this.url}/auth/notifications${urlParams}`, 111 | { headers: { Authorization: `Bearer ${token}` } }); 112 | } 113 | 114 | /** 115 | * Delete all notification associated with an specific identity address 116 | * Requester has to prove to be the owner of the identity adress 117 | * @param {String} token - Session token to be identified 118 | * @return {Object} Http response 119 | */ 120 | deleteNotifications(token: string): AxiosPromise { 121 | return this.deleteFn(`${this.url}/auth/notifications`, { headers: { Authorization: `Bearer ${token}` } }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/api-client/private-folder.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { AxiosPromise } from 'axios'; 3 | 4 | import { axiosGetDebug, axiosPostDebug } from './http-debug'; 5 | 6 | // const nacl = require('tweetnacl'); 7 | const axios = require('axios'); 8 | // const utils = require('../utils'); 9 | // const kcUtils = require('../key-container/kc-utils'); 10 | 11 | type Error = { 12 | error: string, 13 | }; 14 | 15 | /** 16 | * Backup service allows uploading and downloading an encrypted export of the 17 | * wallet database. 18 | */ 19 | class Backup { 20 | url: string; 21 | username: string; 22 | password: string; 23 | debug: boolean; 24 | getFn: (string, any) => any; 25 | postFn: (string, any) => any; 26 | // version: number; 27 | // difficulty: number; 28 | 29 | /** 30 | * @param{String} url - backup service url 31 | * @param{String} username 32 | * @param{String} password 33 | */ 34 | constructor(url: string, username: string, password: string, debug: boolean = false) { 35 | this.url = url; 36 | this.username = username; 37 | this.password = password; 38 | this.debug = debug; 39 | if (this.debug) { 40 | this.getFn = axiosGetDebug; 41 | this.postFn = axiosPostDebug; 42 | } else { 43 | this.getFn = axios.get; 44 | this.postFn = axios.post; 45 | } 46 | } 47 | 48 | /** 49 | * Upload a backup to the backup server. 50 | * @param{String} backup - base64 encoded encrypted backup 51 | * @returns{AxiosPromise} - Promise with http response: empty if OK, otherwise Error 52 | */ 53 | upload(backup: string): AxiosPromise { 54 | const data = { 55 | username: this.username, 56 | password: this.password, 57 | backup, 58 | }; 59 | return this.postFn(`${this.url}/backup/upload`, data); 60 | } 61 | 62 | /** 63 | * Download the current backup from the backup server. 64 | * @returns{AxiosPromise} - Promise with http response: backup if OK, otherwise Error 65 | */ 66 | download(): AxiosPromise { 67 | const data = { 68 | username: this.username, 69 | password: this.password, 70 | }; 71 | return this.postFn(`${this.url}/backup/download`, data); 72 | } 73 | 74 | /** 75 | * Register the username and password to the backup server. 76 | * @returns{AxiosPromise} - Promise with http response: empty if OK, otherwise Error 77 | */ 78 | register(): AxiosPromise<{username: string, password: string}, ?Error> { 79 | const data = { 80 | username: this.username, 81 | password: this.password, 82 | }; 83 | return this.postFn(`${this.url}/register`, data); 84 | } 85 | 86 | // constructor(url: string, version: number = 0) { 87 | // this.url = url; // backup server url 88 | // this.version = version; // current last used version 89 | // this.difficulty = 1; 90 | // } 91 | 92 | // getPoWDifficulty() { 93 | // if (!this.url) { 94 | // console.error('backup url not defined'); 95 | // return undefined; 96 | // } 97 | // const self = this; 98 | // return axios.get(`${this.url}/`, {}).then((res) => { 99 | // self.difficulty = res.data.powdifficulty; 100 | // return res; 101 | // }); 102 | // } 103 | 104 | // /** 105 | // * @param {Object} kc 106 | // * @param {String} idaddr - hex representation of idaddr 107 | // * @param {String} ksign - address of the KSign to use to sign 108 | // * @param {Object} proofOfKSign 109 | // * @param {String} type 110 | // * @param {String} data 111 | // * @param {String} relayAddr 112 | // * @returns {Object} 113 | // */ 114 | // backupData(kc, idAddr, kSign, proofOfKSign, type, data, relayAddr) { 115 | // if (!this.url) { 116 | // console.error('backup url not defined'); 117 | // return null; 118 | // } 119 | // // TODO relayAddr will be set in a global config, not passed in this function as parameter 120 | // /* 121 | // types: 122 | // - claim 123 | // - key/identity 124 | // - received Proof 125 | // - received Claim 126 | // - logs (history) 127 | // - ... 128 | // */ 129 | // this.version += 1; 130 | // const encryptedData = kcUtils.encrypt(kc.encryptionKey, data); 131 | // let dataPacket = { 132 | // idaddrhex: idAddr, 133 | // data: encryptedData, 134 | // datasignature: kc.sign(kSign, encryptedData).signature, 135 | // type, 136 | // ksign: kSign, 137 | // proofofksignhex: proofOfKSign, 138 | // relayaddr: relayAddr, 139 | // version: this.version, // version from the last stored value 140 | // nonce: 0, // will be set in next step of PoW 141 | // }; 142 | // // PoW 143 | // dataPacket = utils.pow(dataPacket, this.difficulty); 144 | 145 | // // send dataPacket to the backend POST /store/{idAddr} 146 | // const self = this; 147 | // return axios.post(`${this.url}/${idAddr}/save`, dataPacket) 148 | // .then((res) => { 149 | // self.version = res.data.version; 150 | // return res.data; 151 | // }) 152 | // .catch((error) => { 153 | // console.error(error); 154 | // throw new Error(error); 155 | // }); 156 | // } 157 | 158 | // /** 159 | // * @param {String} idaddr - hex representation of idaddr 160 | // * @returns {Object} 161 | // */ 162 | // recoverData(idaddr) { 163 | // if (!this.url) { 164 | // console.error('backup url not defined'); 165 | // return undefined; 166 | // } 167 | // return axios.post(`${this.url}/${idaddr}/recover`, {}); 168 | // } 169 | 170 | // /** 171 | // * @param {String} idaddr - hex representation of idaddr 172 | // * @param {Number} version - unixtime 173 | // * @returns {Object} 174 | // */ 175 | // recoverDataSinceVersion(idaddr, version) { 176 | // if (!this.url) { 177 | // console.error('backup url not defined'); 178 | // return undefined; 179 | // } 180 | // return axios.post(`${this.url}/${idaddr}/recover/version/${version}`, {}); 181 | // } 182 | 183 | // /** 184 | // * @param {String} idaddr - hex representation of idaddr 185 | // * @param {String} type - typeof the data 186 | // * @returns {Object} 187 | // */ 188 | // recoverDataByType(idaddr, type) { 189 | // if (!this.url) { 190 | // console.error('backup url not defined'); 191 | // return undefined; 192 | // } 193 | // return axios.post(`${this.url}/${idaddr}/recover/type/${type}`, {}); 194 | // } 195 | } 196 | 197 | module.exports = Backup; 198 | -------------------------------------------------------------------------------- /src/api-client/relay.js: -------------------------------------------------------------------------------- 1 | import { axiosGetDebug, axiosPostDebug } from './http-debug'; 2 | 3 | const axios = require('axios'); 4 | 5 | /** 6 | * Class representing a the Relay 7 | * It contains all the relay API calls 8 | * @param {String} url 9 | */ 10 | class Relay { 11 | /** 12 | * Initialization relay object 13 | * @param {String} url - Relay Url identifier 14 | */ 15 | constructor(url, debug = false) { 16 | this.url = url; 17 | this.debug = debug; 18 | if (this.debug) { 19 | this.getFn = axiosGetDebug; 20 | this.postFn = axiosPostDebug; 21 | } else { 22 | this.getFn = axios.get; 23 | this.postFn = axios.post; 24 | } 25 | } 26 | 27 | /** 28 | * Retrieve the merkle tree root of the relay 29 | * @returns {Object} - Http response 30 | */ 31 | getRelayRoot() { 32 | return this.getFn(`${this.url}/root`); 33 | } 34 | 35 | /** 36 | * Retrieve the merkle tree root of the given identity merkle tree 37 | * @param {String} isAddr - Identity address 38 | * @returns {Object} - Http response 39 | */ 40 | getIdRoot(idAddr) { 41 | return this.getFn(`${this.url}/ids/${idAddr}/root`); 42 | } 43 | 44 | /** 45 | * Add a claim into the identity merkle tree 46 | * @param {String} idAddr - Identity address 47 | * @param {Object} bytesSignedMsg - Data necessary to create the claim: { claim data, signature, key sign } 48 | * @returns {Object} - Http response 49 | */ 50 | postClaim(idAddr, bytesSignedMsg) { 51 | return this.postFn(`${this.url}/ids/${idAddr}/claims`, bytesSignedMsg); 52 | } 53 | 54 | /** 55 | * Retrieve claim data from identity merkle tree 56 | * @param {String} idAddr - Identity address 57 | * @param {String} hi - Claim index hash 58 | * @returns {Object} - Http response 59 | */ 60 | getClaimByHi(idAddr, hi) { 61 | // return this.getFn(`${this.url}/claim_proof/${idAddr}/hi/${hi}`); 62 | return this.getFn(`${this.url}/ids/${idAddr}/claims/${hi}/proof`); 63 | } 64 | 65 | /** 66 | * Creates identity address from given keys through counterfactoual 67 | * It adds automatically the operational key to the identity merkle tree 68 | * @param {String} op - Operation key 69 | * @param {String} dis - Disable key 70 | * @param {String} reen - Reenable key 71 | */ 72 | createId(op, dis, reen) { 73 | const keys = { 74 | operationalPk: op, 75 | kdisable: dis, 76 | kreenable: reen, 77 | }; 78 | 79 | return this.postFn(`${this.url}/ids`, keys); 80 | } 81 | 82 | /** 83 | * Retrieve information about identity 84 | * Information returned is the one necessary to create the counterfactoual 85 | * @param {String} ethAddr 86 | * @returns {Object} Http response 87 | */ 88 | getId(ethAddr) { 89 | return this.getFn(`${this.url}/counterfactuals/${ethAddr}`); 90 | } 91 | 92 | /** 93 | * Deploy smart contract of the given idenity on the blockchain 94 | * @param {String} ethAddr - Identity address 95 | */ 96 | deployId(ethAddr) { 97 | return this.postFn(`${this.url}/counterfactuals/${ethAddr}/deploy`); 98 | } 99 | } 100 | 101 | module.exports = Relay; 102 | -------------------------------------------------------------------------------- /src/auth/auth.js: -------------------------------------------------------------------------------- 1 | // const axios = require('axios'); 2 | // const qrcode = require('qrcode-generator'); 3 | // const WebSocket = require('ws'); // for nodejs tests 4 | // const utils = require('../utils'); 5 | 6 | // /** 7 | // * Generates the challenge with the current unixtime 8 | // * @returns {String} 9 | // */ 10 | // function challenge() { 11 | // const unixTime = Math.round((new Date()).getTime() / 1000); 12 | // const r = Math.random(); 13 | // const randStr = r.toString(36).substr(7, 4); 14 | // return `uuid-${unixTime}-${randStr}`; 15 | // } 16 | 17 | // /** 18 | // * Creates the Auth class object, opens a websocket connection with the backend 19 | // * @param {Object} kc - KeyContainer object 20 | // * @param {String} ksign - KSign address 21 | // * @param {String} url 22 | // * @param {String} wsurl 23 | // * @param {String} wsurl 24 | // * @param {Param} successCallback 25 | // */ 26 | // class Auth { 27 | // constructor(kc, ksign, url, wsurl, successCallback) { 28 | // this.kc = kc; 29 | // this.ksign = ksign; 30 | // this.challenge = challenge(); 31 | // this.signature = kc.sign(ksign, this.challenge).signature; 32 | // this.url = url; 33 | // this.wsurl = `${wsurl}/ws/${this.challenge}`; 34 | // this.successCallback = successCallback; 35 | // this.ws = new WebSocket(this.wsurl); 36 | // this.ws.onmessage = function (msg) { 37 | // const authData = JSON.parse(msg.data); 38 | // successCallback(authData); 39 | // }; 40 | // this.ws.onopen = function () { 41 | // this.send(challenge); 42 | // }; 43 | // this.send = function (data) { 44 | // this.ws.send(data); 45 | // }; 46 | // } 47 | 48 | // /** 49 | // * Returns the QR hex data 50 | // * @returns {String} 51 | // */ 52 | // qrHex() { 53 | // let b = Buffer.from([]); 54 | // b = Buffer.concat([ 55 | // b, 56 | // Buffer.from(this.challenge), 57 | // ]); 58 | // b = Buffer.concat([ 59 | // b, 60 | // utils.hexToBytes(this.signature), 61 | // ]); 62 | // b = Buffer.concat([ 63 | // b, 64 | // Buffer.from(this.url), 65 | // ]); 66 | // return utils.bytesToHex(b); 67 | // } 68 | 69 | // /** 70 | // * Prints the QR hex data into a QR image in a given divId 71 | // * @param {String} - Div id where to place the QR image 72 | // */ 73 | // printQr(divId) { 74 | // const qrHex = this.qrHex(); 75 | // const typeNumber = 9; 76 | // const errorCorrectionLevel = 'L'; 77 | // const qr = qrcode(typeNumber, errorCorrectionLevel); 78 | // qr.addData(qrHex); 79 | // qr.make(); 80 | // document.getElementById(divId).innerHTML = qr.createImgTag(); 81 | // } 82 | // } 83 | 84 | // /** 85 | // * Parses the QR Hex data into an object 86 | // * @param {String} h - Hex string 87 | // * @returns {Object} - Object containing challenge, signature, url 88 | // */ 89 | // function parseQRhex(h) { 90 | // const b = utils.hexToBytes(h); 91 | // return { 92 | // challenge: b.slice(0, 20).toString(), // 20 is the length of the challenge 93 | // signature: utils.bytesToHex(b.slice(20, 85)), 94 | // url: b.slice(85, b.length).toString(), 95 | // }; 96 | // } 97 | 98 | // /** 99 | // * @param {String} url 100 | // * @param {String} idAddr 101 | // * @param {String} challengeStr 102 | // * @param {String} signature 103 | // * @param {String} kSign 104 | // * @param {Object} kSignProof 105 | // * @returns 106 | // */ 107 | // function resolv(url, idAddr, challengeStr, signature, kSign, kSignProof) { 108 | // const authMsg = { 109 | // address: idAddr, 110 | // challenge: challengeStr, 111 | // signature, 112 | // ksign: kSign, 113 | // ksignProof: kSignProof, 114 | // }; 115 | // return axios.post(`${url}/auth`, authMsg); 116 | // } 117 | 118 | // module.exports = { 119 | // Auth, 120 | // parseQRhex, 121 | // resolv, 122 | // }; 123 | -------------------------------------------------------------------------------- /src/auth/dapp.js: -------------------------------------------------------------------------------- 1 | // const axios = require('axios'); 2 | // const nacl = require('tweetnacl'); 3 | // nacl.util = require('tweetnacl-util'); 4 | // const IPFS = require('ipfs'); 5 | // const IPFSRoom = require('ipfs-pubsub-room'); 6 | // // const encoder = new TextEncoder('utf-8'); 7 | // 8 | // /* eslint no-console: "off" */ 9 | // 10 | // /** 11 | // * @param {String} idaddr 12 | // * @param {String} roomQr 13 | // * @param {Param} roomConnectedCallback 14 | // * @param {Param} msgSendCallback 15 | // */ 16 | // class Dapp { 17 | // constructor(idaddr, roomQr, roomConnectedCallback, msgSendCallback) { 18 | // this.peerInfo = ''; 19 | // this.room = ''; 20 | // this.roomid = ''; 21 | // this.nonce = ''; 22 | // this.secretkey = ''; 23 | // this.ipfs = new IPFS({ 24 | // init: true, 25 | // start: true, 26 | // config: { 27 | // Addresses: { 28 | // Swarm: ['/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star'], 29 | // }, 30 | // }, 31 | // EXPERIMENTAL: { 32 | // pubsub: true, 33 | // }, 34 | // }); 35 | // 36 | // this.ipfs.once('ready', () => this.ipfs.id((err, _peerInfo) => { 37 | // if (err) { 38 | // throw err; 39 | // } 40 | // this.peerInfo = _peerInfo; 41 | // toastr.info(`IPFS node started and has ID ${this.peerInfo.id}`); 42 | // 43 | // const go = () => { 44 | // this.ipfs.swarm.addrs().then((peers) => { 45 | // if (peers.length === 0) { 46 | // toastr.info('no peers connected. waiting...'); 47 | // setTimeout(() => go(), 1000); 48 | // } else { 49 | // toastr.info('connected to ipfs pubsub.'); 50 | // // this.parseQr(idaddr, roomQr, roomConnectedCallback, msgSendCallback); 51 | // } 52 | // }); 53 | // }; 54 | // go(); 55 | // })); 56 | // } 57 | // 58 | // /** 59 | // * @param {String} idaddr 60 | // * @param {String} roomQr 61 | // * @param {Param} roomConnectedCallback 62 | // * @param {Param} msgSendCallback 63 | // */ 64 | // parseQr(idaddr, roomQr, roomConnectedCallback, msgSendCallback) { 65 | // this.secretkey = nacl.util.decodeBase64(roomQr); 66 | // this.roomid = nacl.util.encodeBase64(nacl.hash(this.secretkey).slice(48)); 67 | // this.nonce = 0; 68 | // 69 | // this.room = IPFSRoom(this.ipfs, this.roomid); 70 | // this.room.on('subscribed', () => { 71 | // // eslint-disable-next-line no-console 72 | // console.log(`Now connected to room ${this.roomid}`); 73 | // roomConnectedCallback(this.roomid); 74 | // this.room.on('message', message => this.recv(message, this.recvWallet)); 75 | // this.send(`idaddr|${idaddr}`, msgSendCallback); 76 | // }); 77 | // } 78 | // 79 | // /** 80 | // * @param {String} message 81 | // * @param {Param} msgSendCallback 82 | // */ 83 | // send(message, msgSendCallback) { 84 | // // eslint-disable-next-line no-console 85 | // console.log('send', message); 86 | // const nonceBytes = new Uint8Array(nacl.box.nonceLength); 87 | // nonceBytes[nacl.box.nonceLength - 1] = this.nonce & 0xff; 88 | // nonceBytes[nacl.box.nonceLength - 2] = (this.nonce >> 8) & 0xff; 89 | // const ciphertext = nacl.secretbox(encoder.encode(message), nonceBytes, this.secretkey); 90 | // this.room.broadcast(ciphertext); 91 | // this.nonce += 1; 92 | // msgSendCallback(); 93 | // } 94 | // 95 | // /** 96 | // * @param {String} message 97 | // * @param {Param} callback 98 | // */ 99 | // recv(message, callback) { 100 | // if (message.from === this.peerInfo.id) { 101 | // return; 102 | // } 103 | // 104 | // const nonceBytes = new Uint8Array(nacl.box.nonceLength); 105 | // nonceBytes[nacl.box.nonceLength - 1] = this.nonce & 0xff; 106 | // nonceBytes[nacl.box.nonceLength - 2] = (this.nonce >> 8) & 0xff; 107 | // const plaintext = nacl.secretbox.open(message.data, nonceBytes, this.secretkey); 108 | // if (plaintext === null) { 109 | // status('Invalid message recieved', true); 110 | // return; 111 | // } 112 | // this.nonce += 1; 113 | // 114 | // callback(decoder.decode(plaintext)); 115 | // } 116 | // 117 | // async recvWallet(message) { 118 | // status(`wallet got ${message}`, true); 119 | // 120 | // let [op, args] = message.split('|'); 121 | // if (op === 'call') { 122 | // const ksignaddr = '0xee602447b5a75cf4f25367f5d199b860844d10c4'; 123 | // let ksignpvk = ethutil.toBuffer('0x8A85AAA2A8CE0D24F66D3EAA7F9F501F34992BACA0FF942A8EDF7ECE6B91F713'); 124 | // 125 | // args = JSON.parse(args); 126 | // 127 | // // get last nonce for identity 128 | // const idinfo = await axios.get(`${relayurl}/id/${idaddr}`); 129 | // 130 | // fwdnonce = 1 + idinfo.Onchain.LastNonce; 131 | // 132 | // // build signature 133 | // const fwdsigpre = `0x${Buffer.concat([ 134 | // buf(uint8(0x19)), 135 | // buf(uint8(0)), 136 | // buf(idaddr), 137 | // buf(uint256(fwdnonce)), 138 | // buf(args.to), 139 | // buf(args.data), 140 | // buf(uint256(args.value)), 141 | // buf(uint256(args.gas)), 142 | // ]).toString('hex')}`; 143 | // const fwdsig = ethutil.ecsign(buf(sha3(fwdsigpre)), ksignpvk); 144 | // 145 | // args.fwdsig = `0x${Buffer.concat([ 146 | // fwdsig.r, 147 | // fwdsig.s, 148 | // buf(fwdsig.v), 149 | // ]).toString('hex')}`; 150 | // 151 | // // forward call 152 | // const idifwdnoncenfo = await axios.post(`${relayurl}/id/${idaddr}/forward`, args); 153 | // } 154 | // } 155 | // } 156 | // 157 | // module.exports = { 158 | // Dapp, 159 | // }; 160 | // 161 | -------------------------------------------------------------------------------- /src/claim/claim-assign-name.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const bs58 = require('bs58'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | /** 8 | * Class representing an assign name claim 9 | * Assign name claim is used to bind an identity addres with a human readable text 10 | * Assign name entry representation is as follows: 11 | * |element 3|: |empty|version|claim type| - |20 bytes|4 bytes|8 bytes| 12 | * |element 2|: |hash name| - |1 byte|31 bytes| 13 | * |element 1|: |empty|identity| - |1 bytes|31 bytes| 14 | * |element 0|: |empty| - |32 bytes| 15 | */ 16 | export class AssignName { 17 | version: number; 18 | hashName: Buffer; 19 | id: string; 20 | 21 | /** 22 | * Initialize claim data structure from fields 23 | */ 24 | constructor(name: string, id: string) { 25 | this.version = 0; 26 | this.hashName = claimUtils.hashString(name); 27 | claimUtils.checkByteLen(bs58.decode(id), 31); 28 | this.id = id; 29 | } 30 | 31 | /** 32 | * Decode field claim structure into raw data claim structure 33 | * @param {Object} entry - Entry of the claim 34 | * @returns {Object} AssignName class object 35 | */ 36 | static newFromEntry(entry: Entry): AssignName { 37 | // Parse element 3 38 | const { version } = claimUtils.getClaimTypeVersion(entry); 39 | // Parse element 2 40 | const hashName = claimUtils.getElemBuf(entry.elements[2], 0, 31); 41 | // Parse element 1 42 | const id = bs58.encode(claimUtils.getElemBuf(entry.elements[1], 0, 31)); 43 | const claim = new AssignName('', id); 44 | claim.hashName = hashName; 45 | claim.version = version; 46 | return claim; 47 | } 48 | 49 | /** 50 | * Code raw data claim object into an entry claim object 51 | * @returns {Object} Entry representation of the claim 52 | */ 53 | toEntry(): Entry { 54 | const entry = Entry.newEmpty(); 55 | // Entry element 3 composition 56 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.ASSIGN_NAME.TYPE, this.version); 57 | // Entry element 2 composition 58 | claimUtils.copyToElemBuf(entry.elements[2], 0, this.hashName); 59 | // Entry element 1 composition 60 | claimUtils.copyToElemBuf(entry.elements[1], 0, bs58.decode(this.id)); 61 | // Entry element 0 remains as empty value 62 | return entry; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/claim/claim-assign-name.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const claim = require('./claim'); 7 | const utils = require('../utils'); 8 | 9 | const { expect } = chai; 10 | 11 | describe('[Claim Assign Name]', () => { 12 | const versionExample = 1; 13 | const nameExample = 'example.iden3.eth'; 14 | const idExample = '1pnWU7Jdr4yLxp1azs1r1PpvfErxKGRQdcLBZuq3Z'; 15 | let claimAssignName; 16 | let entryClaim; 17 | let parsedClaim; 18 | 19 | before('Create new assign name claim', () => { 20 | claimAssignName = new claim.AssignName(nameExample, idExample); 21 | claimAssignName.version = versionExample; 22 | expect(claimAssignName).to.not.be.equal(null); 23 | entryClaim = claimAssignName.toEntry(); 24 | parsedClaim = claim.AssignName.newFromEntry(entryClaim); 25 | }); 26 | 27 | it('Parse version', () => { 28 | expect(claimAssignName.version).to.be.equal(parsedClaim.version); 29 | }); 30 | it('Parse hash name', () => { 31 | expect(claimAssignName.hashName.toString('hex')).to.be.equal(parsedClaim.hashName.toString('hex')); 32 | }); 33 | it('Parse id address', () => { 34 | expect(claimAssignName.id).to.be.equal(parsedClaim.id); 35 | }); 36 | it('Extract bytes from full element', () => { 37 | const hexFromElement = entryClaim.toHex(); 38 | expect(hexFromElement).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 39 | + '0000041c980d8faa54be797337fa55dbe62a7675e0c83ce5383b78a04b26b9f4' 40 | + '00d67b05d8e2d1ace8f3e84b8451dd2e9da151578c3c6be23e7af11add5a807a' 41 | + '0000000000000000000000000000000000000000000000010000000000000003'); 42 | }); 43 | it('Calculate Hi', () => { 44 | const hi = entryClaim.hi(); 45 | const hiResult = '0x16b32b9bc822ab5c1136eb099c7b05864914d7ee2cc531f932e6264c2d4b65e2'; 46 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 47 | }); 48 | it('Calculate Hv', () => { 49 | const hv = entryClaim.hv(); 50 | const hvResult = '0x0aad8bdcc40190af0a66c21b5fc0da7a3ad11ce0f718d5bc9d9457ca39cfec22'; 51 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 52 | }); 53 | 54 | it('Parse entry into claim assign name', () => { 55 | const hashNameExample = utils.hashBytes(Buffer.from(nameExample, 'utf8')).slice(1, 32); 56 | const entryHex = entryClaim.toHex(); 57 | 58 | const entry = Entry.newFromHex(entryHex); 59 | const c0 = (claim.newClaimFromEntry(entry): any); 60 | expect(c0.version).to.be.equal(1); 61 | expect(c0.id).to.be.equal(idExample); 62 | expect(c0.hashName.toString('hex')).to.be.equal(hashNameExample.toString('hex')); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-eth-key.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const utils = require('../utils'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | /** 8 | * Ethereum key Types 9 | */ 10 | export const ETH_KEY_TYPE = { 11 | DISABLE: 0, 12 | REENABLE: 1, 13 | UPGRADE: 2, 14 | UPDATE_ROOT: 3, 15 | }; 16 | 17 | /** 18 | * Class representing an authorized ethereum key claim 19 | * Ethereum key is defined by: Ethereum key vlue and its type 20 | * Authorized ethereum key elements representation is as follows: 21 | * |element 3|: |empty|version|claim type| - |20 bytes|4 bytes|8 bytes| 22 | * |element 2|: |eth key type|eth key - |4 bytes|20 bytes| 23 | * |element 1|: |empty| - |32 bytes| 24 | * |element 0|: |empty| - |32 bytes| 25 | */ 26 | export class AuthorizeEthKey { 27 | version: number; 28 | ethKey: string; 29 | ethKeyType: number; 30 | 31 | /** 32 | * Initialize claim data structure from fields 33 | */ 34 | constructor(ethKey: string, ethKeyType: number) { 35 | this.version = 0; 36 | claimUtils.checkByteLen(utils.hexToBytes(ethKey), 20); 37 | this.ethKey = ethKey; 38 | this.ethKeyType = ethKeyType; 39 | } 40 | 41 | /** 42 | * Decode field claim structure into raw data claim structure 43 | * @param {Object} entry - Element representation of the claim 44 | * @returns {Object} AuthorizeEthKey class object 45 | */ 46 | static newFromEntry(entry: Entry): AuthorizeEthKey { 47 | // Parse element 3 48 | const { version } = claimUtils.getClaimTypeVersion(entry); 49 | // Parse element 2 50 | const ethKey = `0x${claimUtils.getElemBuf(entry.elements[2], 0, 20).toString('hex')}`; 51 | const ethKeyType = claimUtils.buf2num(claimUtils.getElemBuf(entry.elements[2], 20, 4)); 52 | const claim = new AuthorizeEthKey(ethKey, ethKeyType); 53 | claim.version = version; 54 | return claim; 55 | } 56 | 57 | /** 58 | * Code raw data claim object into an entry claim object 59 | * @returns {Object} Element representation of the claim 60 | */ 61 | toEntry(): Entry { 62 | const entry = Entry.newEmpty(); 63 | // claim element 3 composition 64 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.AUTHORIZE_ETH_KEY.TYPE, this.version); 65 | // claim element 2 composition 66 | claimUtils.copyToElemBuf(entry.elements[2], 0, utils.hexToBytes(this.ethKey)); 67 | claimUtils.copyToElemBuf(entry.elements[2], 20, claimUtils.num2buf(this.ethKeyType)); 68 | // claim element 1 remains as empty value 69 | // claim element 0 remains as empty value 70 | return entry; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-eth-key.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | 4 | const chai = require('chai'); 5 | const claim = require('./claim'); 6 | const utils = require('../utils'); 7 | 8 | const { expect } = chai; 9 | 10 | describe('[Claim Authorize Ethereum Key]', () => { 11 | let entryClaim; 12 | let parsedClaim; 13 | 14 | const ethKey = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 15 | 16 | let claimAuthEthKey; 17 | 18 | before('Create new authorizeEthKey claim', () => { 19 | claimAuthEthKey = new claim.AuthorizeEthKey(ethKey, claim.ETH_KEY_TYPE.UPGRADE); 20 | expect(claimAuthEthKey).to.not.be.equal(null); 21 | entryClaim = claimAuthEthKey.toEntry(); 22 | parsedClaim = claim.AuthorizeEthKey.newFromEntry(entryClaim); 23 | }); 24 | 25 | it('Parse version', () => { 26 | expect(claimAuthEthKey.version).to.be.equal(parsedClaim.version); 27 | }); 28 | it('Parse ethereum address', () => { 29 | expect(claimAuthEthKey.ethKey).to.be.equal(parsedClaim.ethKey); 30 | }); 31 | it('Extract bytes from full element', () => { 32 | const hexFromElement = entryClaim.toHex(); 33 | expect(hexFromElement).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 34 | + '0000000000000000000000000000000000000000000000000000000000000000' 35 | + '000000000000000000000002e0fbce58cfaa72812103f003adce3f284fe5fc7c' 36 | + '0000000000000000000000000000000000000000000000000000000000000009'); 37 | }); 38 | it('Calculate Hi', () => { 39 | const hi = entryClaim.hi(); 40 | const hiResult = '0x0ce27cf2190dfa6ee36276e79335942c28a08dbc5ef8c564ed2f337d5c85b666'; 41 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 42 | }); 43 | it('Calculate Hv', () => { 44 | const hv = entryClaim.hv(); 45 | const hvResult = '0x021a76d5f2cdcf354ab66eff7b4dee40f02501545def7bb66b3502ae68e1b781'; 46 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-ksign-babyjub.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const utils = require('../utils'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | /** 8 | * Class representing an authorized Ksign claim with babyjub curve 9 | * Authorized Ksign Babyjub claim is used to authorize a public key for being used afterwards 10 | * Public key is stored in its compress format composed by Ay ( coordinate ) and its sign 11 | * Authorized Ksign Babyjub element representation is as follows: 12 | * |element 3|: |empty|sign|version|claim type| - |19 bytes|1 bytes|4 bytes|8 bytes| 13 | * |element 2|: |Ay| - |32 bytes| 14 | * |element 1|: |empty| - |32 bytes| 15 | * |element 0|: |empty| - |32 bytes| 16 | */ 17 | export class AuthorizeKSignBabyJub { 18 | sign: boolean; 19 | version: number; 20 | ay: Buffer; 21 | 22 | /** 23 | * Initialize claim data structure from fields 24 | * pubKComp is in little endian 25 | */ 26 | constructor(pubKComp: string) { 27 | const pubKCompBuf = utils.swapEndianness(utils.hexToBytes(pubKComp)); 28 | claimUtils.checkByteLen(pubKCompBuf, 32); 29 | this.sign = (pubKCompBuf[0] & 0x80) !== 0; 30 | pubKCompBuf[0] &= 0x7F; 31 | this.ay = pubKCompBuf; 32 | this.version = 0; 33 | } 34 | 35 | /** 36 | * Decode field claim structure into raw data claim structure 37 | * @param {Object} entry - Element representation of the claim 38 | * @returns {Object} AuthorizeKSignBabyJub class object 39 | */ 40 | static newFromEntry(entry: Entry): AuthorizeKSignBabyJub { 41 | // Parse element 3 42 | const { version } = claimUtils.getClaimTypeVersion(entry); 43 | // Parse element 2 44 | let ay = Buffer.from(claimUtils.getElemBuf(entry.elements[2], 0, 32)); 45 | const sign = claimUtils.getElemBuf(entry.elements[3], 8 + 4, 1)[0] !== 0; 46 | ay[0] |= sign ? 0x80 : 0x00; 47 | ay = utils.swapEndianness(ay); 48 | const claim = new AuthorizeKSignBabyJub(ay.toString('hex')); 49 | claim.version = version; 50 | return claim; 51 | } 52 | 53 | /** 54 | * Code raw data claim object into an entry claim object 55 | * @returns {Object} Element representation of the claim 56 | */ 57 | toEntry(): Entry { 58 | const entry = Entry.newEmpty(); 59 | // claim element 3 composition 60 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.AUTHORIZE_KSIGN_BABYJUB.TYPE, this.version); 61 | claimUtils.copyToElemBuf(entry.elements[3], 8 + 4, Buffer.from([this.sign ? 1 : 0])); 62 | // claim element 2 composition 63 | claimUtils.copyToElemBuf(entry.elements[2], 0, this.ay); 64 | // claim element 1 remains as empty value 65 | // claim element 0 remains as empty value 66 | return entry; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-ksign-babyjub.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const claim = require('./claim'); 7 | const utils = require('../utils'); 8 | const eddsa = require('../crypto/eddsa-babyjub.js'); 9 | 10 | const { expect } = chai; 11 | 12 | describe('[Claim Authorize KSign Babyjubjub]', () => { 13 | const versionExample = 1; 14 | const privKey = '0x28156abe7fe2fd433dc9df969286b96666489bac508612d0e16593e944c4f69f'; 15 | const sk = new eddsa.PrivateKey(utils.hexToBytes(privKey)); 16 | const pk = sk.public(); 17 | 18 | const pubKey = pk.toString(); 19 | let claimAuthKSignBabyJub; 20 | let entryClaim; 21 | let parsedClaim; 22 | 23 | before('Create new authorizeKSign claim', () => { 24 | claimAuthKSignBabyJub = new claim.AuthorizeKSignBabyJub(pubKey); 25 | claimAuthKSignBabyJub.version = versionExample; 26 | expect(claimAuthKSignBabyJub).to.not.be.equal(null); 27 | entryClaim = claimAuthKSignBabyJub.toEntry(); 28 | parsedClaim = claim.AuthorizeKSignBabyJub.newFromEntry(entryClaim); 29 | }); 30 | 31 | it('Parse version', () => { 32 | expect(claimAuthKSignBabyJub.version).to.be.equal(parsedClaim.version); 33 | }); 34 | it('Parse sign', () => { 35 | expect(claimAuthKSignBabyJub.sign).to.be.equal(parsedClaim.sign); 36 | }); 37 | it('Parse Ay', () => { 38 | expect(claimAuthKSignBabyJub.ay.toString('hex')).to.be.equal(parsedClaim.ay.toString('hex')); 39 | }); 40 | it('Extract bytes from full element', () => { 41 | const hexFromElement = entryClaim.toHex(); 42 | expect(hexFromElement).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 43 | + '0000000000000000000000000000000000000000000000000000000000000000' 44 | + '2d9e82263b94a343ee95d56c810a5a0adb63a439cd5b4944dfb56f09e28c6f04' 45 | + '0000000000000000000000000000000000000001000000010000000000000001'); 46 | }); 47 | it('Calculate Hi', () => { 48 | const hi = entryClaim.hi(); 49 | const hiResult = '0x1da8cb501998d7cdeb95ab0c65afc0277632ac87d2d74e7a68a0cdca4287649d'; 50 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 51 | }); 52 | it('Calculate Hv', () => { 53 | const hv = entryClaim.hv(); 54 | const hvResult = '0x021a76d5f2cdcf354ab66eff7b4dee40f02501545def7bb66b3502ae68e1b781'; 55 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 56 | }); 57 | it('Parse entry into claim authorize key sign babyjub', () => { 58 | const entryHex = entryClaim.toHex(); 59 | const entry = Entry.newFromHex(entryHex); 60 | const c0 = (claim.newClaimFromEntry(entry): any); 61 | expect(c0.version).to.be.equal(1); 62 | expect(c0.sign).to.be.equal(true); 63 | expect(c0.ay.toString('hex')).to.be.equal('2d9e82263b94a343ee95d56c810a5a0adb63a439cd5b4944dfb56f09e28c6f04'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-ksign-secp256k1.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const utils = require('../utils'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | /** 8 | * Class representing an authorized Ksign claim with elliptic curve as secp256k1 9 | * This claim aims to use ethereum public key until zkSnarks are implemented using a Jubjub curve 10 | * Authorized KsignSecp256k1 claim is used to authorize a public key that belongs to elliptic curve secp256k1 for being used afterwards 11 | * Authorized KsignSecp256k1 element representation is as follows: 12 | * |element 3|: |empty|public key[0]|version|claim type| - |18 bytes|2 bytes|4 bytes|8 bytes| 13 | * |element 2|: |empty|public key[1]| - |1 bytes|31 bytes| 14 | * |element 1|: |empty| - |32 bytes| 15 | * |element 0|: |empty| - |32 bytes| 16 | */ 17 | export class AuthorizeKSignSecp256k1 { 18 | version: number; 19 | pubKeyComp: Buffer; 20 | 21 | /** 22 | * Initialize claim data structure from fields 23 | */ 24 | constructor(pubKeyCompHex: string | Buffer) { 25 | if (pubKeyCompHex instanceof Buffer) { 26 | claimUtils.checkByteLen(pubKeyCompHex, 33); 27 | this.pubKeyComp = pubKeyCompHex; 28 | } else { 29 | const pubKeyCompBuff = utils.hexToBytes(pubKeyCompHex); 30 | claimUtils.checkByteLen(pubKeyCompBuff, 33); 31 | this.pubKeyComp = pubKeyCompBuff; 32 | } 33 | this.version = 0; 34 | } 35 | 36 | /** 37 | * Decode field claim structure into raw data claim structure 38 | * @param {Object} entry - Element representation of the claim 39 | * @returns {Object} AuthorizeKSign class object 40 | */ 41 | static newFromEntry(entry: Entry): AuthorizeKSignSecp256k1 { 42 | // Parse element 3 43 | const { version } = claimUtils.getClaimTypeVersion(entry); 44 | // Parse element 3 and element 2 45 | const pubKeyCompBuf = Buffer.concat( 46 | [claimUtils.getElemBuf(entry.elements[2], 0, 31), claimUtils.getElemBuf(entry.elements[3], 8 + 4, 2)], 47 | ); 48 | const claim = new AuthorizeKSignSecp256k1(pubKeyCompBuf); 49 | claim.version = version; 50 | return claim; 51 | } 52 | 53 | /** 54 | * Code raw data claim object into an entry claim object 55 | * @returns {Object} Element representation of the claim 56 | */ 57 | toEntry(): Entry { 58 | const entry = Entry.newEmpty(); 59 | // claim element 3 composition 60 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.AUTHORIZE_KSIGN_SECP256K1.TYPE, this.version); 61 | claimUtils.copyToElemBuf(entry.elements[3], 8 + 4, this.pubKeyComp.slice(this.pubKeyComp.length - 2)); 62 | // claim element 2 composition 63 | claimUtils.copyToElemBuf(entry.elements[2], 0, this.pubKeyComp.slice(0, this.pubKeyComp.length - 2)); 64 | // claim element 1 remains as empty value 65 | // claim element 0 remains as empty value 66 | return entry; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/claim/claim-authorize-ksign-secp256k1.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const ethUtil = require('ethereumjs-util'); 7 | const claim = require('./claim'); 8 | const utils = require('../utils'); 9 | 10 | const { expect } = chai; 11 | const { secp256k1 } = ethUtil; 12 | 13 | describe('[Claim Authorize KSignSecp256k1]', () => { 14 | let entryClaim; 15 | let parsedClaim; 16 | const versionExample = 1; 17 | const privKeyHex = '0x79156abe7fe2fd433dc9df969286b96666489bac508612d0e16593e944c4f69f'; 18 | const privKeyBuff = utils.hexToBytes(privKeyHex); 19 | const pubKeyCompressedExample = secp256k1.publicKeyCreate(privKeyBuff, true); 20 | let claimAuthKSignSecp256k1; 21 | 22 | before('Create new authorizeKSignSecp256k1 claim', () => { 23 | claimAuthKSignSecp256k1 = new claim.AuthorizeKSignSecp256k1(utils.bytesToHex(pubKeyCompressedExample)); 24 | claimAuthKSignSecp256k1.version = versionExample; 25 | expect(claimAuthKSignSecp256k1).to.not.be.equal(null); 26 | entryClaim = claimAuthKSignSecp256k1.toEntry(); 27 | parsedClaim = claim.AuthorizeKSignSecp256k1.newFromEntry(entryClaim); 28 | }); 29 | 30 | it('Check public key compressed', () => { 31 | expect(utils.bytesToHex(pubKeyCompressedExample)).to.be.equal('0x036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d59'); 32 | }); 33 | 34 | it('Parse version', () => { 35 | expect(claimAuthKSignSecp256k1.version).to.be.equal(parsedClaim.version); 36 | }); 37 | it('Parse public key compressed', () => { 38 | expect(claimAuthKSignSecp256k1.pubKeyComp.toString('hex')).to.be.equal(parsedClaim.pubKeyComp.toString('hex')); 39 | }); 40 | it('Extract bytes from full element', () => { 41 | const hexFromElement = entryClaim.toHex(); 42 | expect(hexFromElement).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 43 | + '0000000000000000000000000000000000000000000000000000000000000000' 44 | + '00036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd' 45 | + '0000000000000000000000000000000000004d59000000010000000000000004'); 46 | }); 47 | it('Calculate Hi', () => { 48 | const hi = entryClaim.hi(); 49 | const hiResult = '0x2a65c16ad6d4333877bb94e1753ef79f54b694771a8e46e3c67c1cc59e76985e'; 50 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 51 | }); 52 | it('Calculate Hv', () => { 53 | const hv = entryClaim.hv(); 54 | const hvResult = '0x021a76d5f2cdcf354ab66eff7b4dee40f02501545def7bb66b3502ae68e1b781'; 55 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 56 | }); 57 | it('Parse entry into claim authorize key sign secp256k1', () => { 58 | const entryHex = entryClaim.toHex(); 59 | const entry = Entry.newFromHex(entryHex); 60 | const c0 = (claim.newClaimFromEntry(entry): any); 61 | expect(c0.pubKeyComp.toString('hex')).to.be.equal('036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd4d59'); 62 | expect(c0.version).to.be.equal(1); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/claim/claim-basic.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const claimUtils = require('./claim-utils'); 5 | 6 | /** 7 | * Class representing a basic claim 8 | * Basic claim is used to issue generic data 9 | * Index and Data are split into two fields to fit claim element data structure 10 | * Basic entry representation is as follows: 11 | * |element 3|: |empty|index[0]|version|claim type| - |1 byte|19 bytes|4 bytes|8 bytes| 12 | * |element 2|: |empty|index[1]| - |1 bytes|31 bytes| 13 | * |element 1|: |empty|data[0]| - |1 bytes|31 bytes| 14 | * |element 0|: |empty|data[1]| - |1 bytes|31 bytes| 15 | */ 16 | export class Basic { 17 | version: number; 18 | index: Buffer; 19 | extraData: Buffer; 20 | 21 | /** 22 | * Initialize claim data structure from fields 23 | */ 24 | constructor(index: Buffer, extraData: Buffer) { 25 | claimUtils.checkByteLen(index, 50); 26 | this.index = index; 27 | claimUtils.checkByteLen(extraData, 62); 28 | this.extraData = extraData; 29 | this.version = 0; 30 | } 31 | 32 | /** 33 | * Decode field claim structure into raw data claim structure 34 | * @param {Object} entry - Entry of the claim 35 | * @returns {Object} SetRootKey class object 36 | */ 37 | static newFromEntry(entry: Entry): Basic { 38 | // Parse element 3 39 | const { version } = claimUtils.getClaimTypeVersion(entry); 40 | // Parse element 3 and element 2 41 | const index = Buffer.concat( 42 | [claimUtils.getElemBuf(entry.elements[2], 0, 31), claimUtils.getElemBuf(entry.elements[3], 8 + 4, 19)], 43 | ); 44 | // Parse element 1 and element 0 45 | const extraData = Buffer.concat( 46 | [claimUtils.getElemBuf(entry.elements[0], 0, 31), claimUtils.getElemBuf(entry.elements[1], 0, 31)], 47 | ); 48 | const claim = new Basic(index, extraData); 49 | claim.version = version; 50 | return claim; 51 | } 52 | 53 | /** 54 | * Code raw data claim object into an entry claim object 55 | * @returns {Object} Entry representation of the claim 56 | */ 57 | toEntry(): Entry { 58 | const entry = Entry.newEmpty(); 59 | // Entry element 3 composition 60 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.BASIC.TYPE, this.version); 61 | claimUtils.copyToElemBuf(entry.elements[3], 4 + 8, this.index.slice(this.index.length - 19)); 62 | // Entry element 2 composition 63 | claimUtils.copyToElemBuf(entry.elements[2], 0, this.index.slice(0, this.index.length - 19)); 64 | // Entry element 1 composition 65 | claimUtils.copyToElemBuf(entry.elements[1], 0, this.extraData.slice(this.extraData.length - 31)); 66 | // Entry element 0 composition 67 | claimUtils.copyToElemBuf(entry.elements[0], 0, this.extraData.slice(0, this.extraData.length - 31)); 68 | return entry; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/claim/claim-basic.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const claim = require('./claim'); 7 | const utils = require('../utils'); 8 | 9 | const { expect } = chai; 10 | 11 | describe('[Claim Basic]', () => { 12 | const versionExample = 1; 13 | const indexExample = Buffer.alloc(50); 14 | indexExample.fill(41, 0, 1); 15 | indexExample.fill(42, 1, 49); 16 | indexExample.fill(43, 49, 50); 17 | const dataExample = Buffer.alloc(62); 18 | dataExample.fill(86, 0, 1); 19 | dataExample.fill(88, 1, 61); 20 | dataExample.fill(89, 61, 62); 21 | let claimBasic; 22 | let entryClaim; 23 | let parsedClaim; 24 | 25 | before('Create new basic claim', () => { 26 | claimBasic = new claim.Basic(indexExample, dataExample); 27 | claimBasic.version = versionExample; 28 | expect(claimBasic).to.not.be.equal(null); 29 | entryClaim = claimBasic.toEntry(); 30 | parsedClaim = claim.Basic.newFromEntry(entryClaim); 31 | }); 32 | 33 | it('Parse version', () => { 34 | expect(claimBasic.version).to.be.equal(parsedClaim.version); 35 | }); 36 | it('Parse index slot', () => { 37 | expect(utils.bytesToHex(claimBasic.index)).to.be.equal(utils.bytesToHex(parsedClaim.index)); 38 | }); 39 | it('Parse extra data slot', () => { 40 | expect(utils.bytesToHex(claimBasic.extraData)).to.be.equal(utils.bytesToHex(parsedClaim.extraData)); 41 | }); 42 | it('Extract bytes from full element', () => { 43 | const hexFromElement = entryClaim.toHex(); 44 | expect(hexFromElement).to.be.equal('0x0056585858585858585858585858585858585858585858585858585858585858' 45 | + '0058585858585858585858585858585858585858585858585858585858585859' 46 | + '00292a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' 47 | + '002a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2b000000010000000000000000'); 48 | }); 49 | it('Calculate Hi', () => { 50 | const hi = entryClaim.hi(); 51 | const hiResult = '0x0f6cde1ae964e8a0ab0bbc869ee28c7543853b36b952d2b411300d281686ccdd'; 52 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 53 | }); 54 | it('Calculate Hv', () => { 55 | const hv = entryClaim.hv(); 56 | const hvResult = '0x04ded2a4d7ae573c9db6686796e3a9f03a5142a684fccfc38f59fedee611c07d'; 57 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 58 | }); 59 | it('Parse entry into basic claim', () => { 60 | const entryHex = entryClaim.toHex(); 61 | const entry = Entry.newFromHex(entryHex); 62 | const c0 = (claim.newClaimFromEntry(entry): any); 63 | expect(c0.version).to.be.equal(1); 64 | expect(utils.bytesToHex(c0.index)).to.be.equal(utils.bytesToHex(indexExample)); 65 | expect(utils.bytesToHex(c0.extraData)).to.be.equal(utils.bytesToHex(dataExample)); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/claim/claim-link-object-identity.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const bs58 = require('bs58'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | export const TYPE_OBJECT = { 8 | passport: 0, 9 | address: 1, 10 | phone: 2, 11 | dob: 3, 12 | givenName: 4, 13 | familyName: 5, 14 | certificate_A: 6, 15 | storage: 7, 16 | }; 17 | 18 | /** 19 | * Class representing an object linked to an identity 20 | * Hash object is used to store an object representation through a hash 21 | * Link object identity entry representation is as follows: 22 | * |element 3|: |empty|object index|object type|version|claim type| - |14 bytes|2 bytes|4 bytes|4 bytes|8 bytes| 23 | * |element 2|: |empty|identity| - |1 empty|31 bytes| 24 | * |element 1|: |hash object| - |32 bytes| 25 | * |element 0|: |auxData| - |32 bytes| 26 | */ 27 | export class LinkObjectIdentity { 28 | version: number; 29 | objectType: number; 30 | objectIndex: number; 31 | id: string; 32 | objectHash: Buffer; 33 | auxData: Buffer; 34 | 35 | /** 36 | * Initialize raw claim data structure 37 | * Bytes are taken according entry claim structure 38 | * @param {number} objectType - indicates object associated with object hash 39 | * @param {number} objectIndex - instance of the object 40 | * @param {string} id - identity address linked to object 41 | * @param {Buffer} objectHash - Hash representing the object 42 | * @param {Buffer} auxData - Auxiliary data to complement hash object 43 | */ 44 | constructor(objectType: number, objectIndex: number, id: string, objectHash: Buffer, auxData: Buffer) { 45 | this.objectType = objectType; 46 | this.objectIndex = objectIndex; 47 | claimUtils.checkByteLen(bs58.decode(id), 31); 48 | this.id = id; 49 | claimUtils.checkElemFitsClaim(objectHash); 50 | this.objectHash = objectHash; 51 | claimUtils.checkElemFitsClaim(auxData); 52 | this.auxData = auxData; 53 | this.version = 0; 54 | } 55 | 56 | /** 57 | * Decode field claim structure into raw data claim structure 58 | * @param {Object} entry - Entry of the claim 59 | * @returns {Object} SetRootKey class object 60 | */ 61 | static newFromEntry(entry: Entry) { 62 | // Parse element 3 63 | const { version } = claimUtils.getClaimTypeVersion(entry); 64 | const objectType = claimUtils.buf2num(claimUtils.getElemBuf(entry.elements[3], 8 + 4, 4)); 65 | const objectIndex = claimUtils.buf2num(claimUtils.getElemBuf(entry.elements[3], 8 + 4 + 4, 2)); 66 | // Parse element 2 67 | const id = bs58.encode(claimUtils.getElemBuf(entry.elements[2], 0, 31)); 68 | // Parse element 1 69 | const objectHash = claimUtils.getElemBuf(entry.elements[1], 0, 32); 70 | // Parse element 0 71 | const auxData = claimUtils.getElemBuf(entry.elements[0], 0, 32); 72 | const claim = new LinkObjectIdentity(objectType, objectIndex, id, objectHash, auxData); 73 | claim.version = version; 74 | return claim; 75 | } 76 | 77 | /** 78 | * Code raw data claim object into an entry claim object 79 | * @returns {Object} Entry representation of the claim 80 | */ 81 | toEntry(): Entry { 82 | const entry = Entry.newEmpty(); 83 | // Entry element 3 composition 84 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.LINK_OBJECT_IDENTITY.TYPE, this.version); 85 | claimUtils.copyToElemBuf(entry.elements[3], 8 + 4, claimUtils.num2buf(this.objectType)); 86 | claimUtils.copyToElemBuf(entry.elements[3], 8 + 4 + 4, claimUtils.num2buf2(this.objectIndex)); 87 | // Entry element 2 composition 88 | claimUtils.copyToElemBuf(entry.elements[2], 0, bs58.decode(this.id)); 89 | // Entry element 1 composition 90 | claimUtils.copyToElemBuf(entry.elements[1], 0, this.objectHash); 91 | // Entry element 0 composition 92 | claimUtils.copyToElemBuf(entry.elements[0], 0, this.auxData); 93 | return entry; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/claim/claim-link-object-identity.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | 4 | const chai = require('chai'); 5 | const claim = require('./claim'); 6 | const utils = require('../utils'); 7 | 8 | const { expect } = chai; 9 | 10 | describe('[Claim link object identity Id]', () => { 11 | const versionExample = 1; 12 | const objectTypeExample = 1; 13 | const objectIndexExample = 0; 14 | const hashObjectExample = utils.hexToBytes('0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c'); 15 | const idExample = '1pnWU7Jdr4yLxp1azs1r1PpvfErxKGRQdcLBZuq3Z'; 16 | const auxDataExample = utils.hexToBytes('0x0f0102030405060708090a0b0c0d0e0f01020304050607090a0b0c0d0e0f0102'); 17 | let claimLinkObject; 18 | let entryClaim; 19 | let parsedClaim; 20 | 21 | before('Create new unique id claim', () => { 22 | claimLinkObject = new claim.LinkObjectIdentity( 23 | objectTypeExample, objectIndexExample, idExample, hashObjectExample, auxDataExample, 24 | ); 25 | claimLinkObject.version = versionExample; 26 | expect(claimLinkObject).to.not.be.equal(null); 27 | entryClaim = claimLinkObject.toEntry(); 28 | parsedClaim = claim.LinkObjectIdentity.newFromEntry(entryClaim); 29 | }); 30 | 31 | it('Parse version', () => { 32 | expect(claimLinkObject.version).to.be.equal(parsedClaim.version); 33 | }); 34 | it('Parse object type', () => { 35 | expect(claimLinkObject.objectType).to.be.equal(parsedClaim.objectType); 36 | }); 37 | it('Parse object index', () => { 38 | expect(claimLinkObject.objectIndex).to.be.equal(parsedClaim.objectIndex); 39 | }); 40 | it('Parse identity address', () => { 41 | expect(claimLinkObject.id).to.be.equal(parsedClaim.id); 42 | }); 43 | it('Parse object hash identifier', () => { 44 | expect(utils.bytesToHex(claimLinkObject.objectHash)).to.be.equal(utils.bytesToHex(parsedClaim.objectHash)); 45 | }); 46 | it('Parse auxiliary data', () => { 47 | expect(utils.bytesToHex(claimLinkObject.auxData)).to.be.equal(utils.bytesToHex(parsedClaim.auxData)); 48 | }); 49 | it('Extract bytes from full element', () => { 50 | const hexFromElement = entryClaim.toHex(); 51 | expect(hexFromElement).to.be.equal('0x0f0102030405060708090a0b0c0d0e0f01020304050607090a0b0c0d0e0f0102' 52 | + '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c' 53 | + '0000041c980d8faa54be797337fa55dbe62a7675e0c83ce5383b78a04b26b9f4' 54 | + '0000000000000000000000000000000000000001000000010000000000000005'); 55 | }); 56 | it('Calculate Hi', () => { 57 | const hi = entryClaim.hi(); 58 | const hiResult = '0x212ccd565460d55b41c570b54fd0015f71729cb96e2db08120559a279c188012'; 59 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 60 | }); 61 | it('Calculate Hv', () => { 62 | const hv = entryClaim.hv(); 63 | const hvResult = '0x056fd73d70b5ece7889ceda6a161fb26f9cb33fc3cd1f9ca252a7665a43be70b'; 64 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/claim/claim-set-root-key.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const bs58 = require('bs58'); 5 | const claimUtils = require('./claim-utils'); 6 | 7 | /** 8 | * Class representing a set root key claim 9 | * Set root key claim is used to commit a root of a merkle by a given identity 10 | * Set root key name entry representation is as follows: 11 | * |element 3|: |empty|era|version|claim type| - |16 bytes|4 bytes|4 bytes|8 bytes| 12 | * |element 2|: |empty|identity| - |1 byte|31 bytes| 13 | * |element 1|: |root key| - |32 bytes| 14 | * |element 0|: |empty| - |32 bytes| 15 | */ 16 | export class SetRootKey { 17 | version: number; 18 | era: number; 19 | id: string; 20 | rootKey: Buffer; 21 | 22 | /** 23 | * Initialize claim data structure from fields 24 | */ 25 | constructor(id: string, rootKey: Buffer) { 26 | claimUtils.checkByteLen(bs58.decode(id), 31); 27 | this.id = id; 28 | claimUtils.checkElemFitsClaim(rootKey); 29 | this.rootKey = rootKey; 30 | this.version = 0; 31 | this.era = 0; 32 | } 33 | 34 | /** 35 | * Decode field claim structure into raw data claim structure 36 | * @param {Object} entry - Entry of the claim 37 | * @returns {Object} SetRootKey class object 38 | */ 39 | static newFromEntry(entry: Entry) { 40 | // Parse element 3 41 | const { version } = claimUtils.getClaimTypeVersion(entry); 42 | const era = claimUtils.buf2num(claimUtils.getElemBuf(entry.elements[3], 8 + 4, 4)); 43 | // Parse element 2 44 | const id = bs58.encode(claimUtils.getElemBuf(entry.elements[2], 0, 31)); 45 | // Parse element 1 46 | const rootKey = claimUtils.getElemBuf(entry.elements[1], 0, 32); 47 | const claim = new SetRootKey(id, rootKey); 48 | claim.version = version; 49 | claim.era = era; 50 | return claim; 51 | } 52 | 53 | /** 54 | * Code raw data claim object into an entry claim object 55 | * @returns {Object} Entry representation of the claim 56 | */ 57 | toEntry(): Entry { 58 | const entry = Entry.newEmpty(); 59 | // Entry element 3 composition 60 | claimUtils.setClaimTypeVersion(entry, claimUtils.CLAIMTYPES.SET_ROOT_KEY.TYPE, this.version); 61 | claimUtils.copyToElemBuf(entry.elements[3], 8 + 4, claimUtils.num2buf(this.era)); 62 | // Entry element 2 composition 63 | claimUtils.copyToElemBuf(entry.elements[2], 0, bs58.decode(this.id)); 64 | // Entry element 1 composition 65 | claimUtils.copyToElemBuf(entry.elements[1], 0, this.rootKey); 66 | // Entry element 0 remains as empty value 67 | return entry; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/claim/claim-set-root-key.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const claim = require('./claim'); 7 | const utils = require('../utils'); 8 | 9 | const { expect } = chai; 10 | 11 | describe('[Claim Set root key]', () => { 12 | const versionExample = 1; 13 | const eraExample = 1; 14 | const idExample = '1pnWU7Jdr4yLxp1azs1r1PpvfErxKGRQdcLBZuq3Z'; 15 | const rootKeyExample = utils.hexToBytes('0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c'); 16 | let claimSetRootKey; 17 | let entryClaim; 18 | let parsedClaim; 19 | 20 | before('Create new set root claim', () => { 21 | claimSetRootKey = new claim.SetRootKey(idExample, rootKeyExample); 22 | claimSetRootKey.version = versionExample; 23 | claimSetRootKey.era = eraExample; 24 | expect(claimSetRootKey).to.not.be.equal(null); 25 | entryClaim = claimSetRootKey.toEntry(); 26 | parsedClaim = claim.SetRootKey.newFromEntry(entryClaim); 27 | }); 28 | 29 | it('Parse version', () => { 30 | expect(claimSetRootKey.version).to.be.equal(parsedClaim.version); 31 | }); 32 | it('Parse era', () => { 33 | expect(claimSetRootKey.era).to.be.equal(parsedClaim.era); 34 | }); 35 | it('Parse id address', () => { 36 | expect(claimSetRootKey.id).to.be.equal(parsedClaim.id); 37 | }); 38 | it('Parse rootKey', () => { 39 | expect(utils.bytesToHex(claimSetRootKey.rootKey)).to.be.equal(utils.bytesToHex(parsedClaim.rootKey)); 40 | }); 41 | it('Extract bytes from full element', () => { 42 | const hexFromElement = entryClaim.toHex(); 43 | expect(hexFromElement).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 44 | + '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c' 45 | + '0000041c980d8faa54be797337fa55dbe62a7675e0c83ce5383b78a04b26b9f4' 46 | + '0000000000000000000000000000000000000001000000010000000000000002'); 47 | }); 48 | it('Calculate Hi', () => { 49 | const hi = entryClaim.hi(); 50 | const hiResult = '0x10c37d494bc5a16d4c355766cc564651c1371202d9bc4b1991993bbdd25506b9'; 51 | expect(utils.bytesToHex(hi)).to.be.equal(hiResult); 52 | }); 53 | it('Calculate Hv', () => { 54 | const hv = entryClaim.hv(); 55 | const hvResult = '0x23af6c51c0ffe40d81508bf39e0360f884c9a1766895a8897a5e78da7bb611fa'; 56 | expect(utils.bytesToHex(hv)).to.be.equal(hvResult); 57 | }); 58 | it('Parse entry into claim set root key', () => { 59 | const entryHex = entryClaim.toHex(); 60 | const entry = Entry.newFromHex(entryHex); 61 | const c0 = (claim.newClaimFromEntry(entry): any); 62 | expect(c0.version).to.be.equal(1); 63 | expect(c0.era).to.be.equal(1); 64 | expect(c0.id).to.be.equal('1pnWU7Jdr4yLxp1azs1r1PpvfErxKGRQdcLBZuq3Z'); 65 | expect(utils.bytesToHex(c0.rootKey)).to.be.equal('0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0c'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/claim/claim-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | 4 | const { babyJub } = require('circomlib'); 5 | const snarkjs = require('snarkjs'); 6 | const utils = require('../utils'); 7 | 8 | const { bigInt } = snarkjs; 9 | 10 | export const CLAIMTYPES = { 11 | BASIC: { 12 | DEF: 'basic', 13 | TYPE: 0, 14 | }, 15 | AUTHORIZE_KSIGN_BABYJUB: { 16 | DEF: 'authorizeKSignBabyJub', 17 | TYPE: 1, 18 | }, 19 | SET_ROOT_KEY: { 20 | DEF: 'setRootKey', 21 | TYPE: 2, 22 | }, 23 | ASSIGN_NAME: { 24 | DEF: 'assignName', 25 | TYPE: 3, 26 | }, 27 | AUTHORIZE_KSIGN_SECP256K1: { 28 | DEF: 'authorizeKSignSecp256k1', 29 | TYPE: 4, 30 | }, 31 | LINK_OBJECT_IDENTITY: { 32 | DEF: 'linkObjectIdentity', 33 | TYPE: 5, 34 | }, 35 | AUTHORIZE_ETH_KEY: { 36 | DEF: 'authorizeEthKey', 37 | TYPE: 9, 38 | }, 39 | }; 40 | 41 | /** 42 | * Decode a buffer as number in big endian 43 | * @param {Buffer} Buffer 44 | * @returns {number} 45 | */ 46 | export function buf2num(buf: Buffer): number { 47 | return Number(utils.bufferToBigIntBE(buf)); 48 | } 49 | 50 | /** 51 | * Encode a number as a 4 byte buffer in big endian 52 | * @param {number} num 53 | * @returns {Buffer} 54 | */ 55 | export function num2buf(num: number): Buffer { 56 | const buf = Buffer.alloc(4); 57 | buf.writeUInt32BE(num, 0); 58 | return buf; 59 | } 60 | 61 | /** 62 | * Encode a number as a 2 byte buffer in big endian 63 | * @param {number} num 64 | * @returns {Buffer} 65 | */ 66 | export function num2buf2(num: number): Buffer { 67 | const buf = Buffer.alloc(2); 68 | buf.writeUInt16BE(num, 0); 69 | return buf; 70 | } 71 | 72 | /** 73 | * Hash a string for a claim 74 | * @param {string} elem 75 | * @returns {Buffer} hash 76 | */ 77 | export function hashString(s: string): Buffer { 78 | return utils.hashBytes(Buffer.from(s, 'utf8')).slice(1); 79 | } 80 | 81 | /** 82 | * Copy a buffer to an entry element ending at position start 83 | * @param {Buffer} elem 84 | * @param {number} start 85 | * @param {Buffer} src 86 | */ 87 | export function copyToElemBuf(elem: Buffer, start: number, src: Buffer) { 88 | elem.fill(src, 32 - start - src.length, 32 - start); 89 | } 90 | 91 | /** 92 | * Get a buffer from an entry element ending at position start 93 | * @param {Buffer} elem 94 | * @param {number} start 95 | * @param {number} length 96 | */ 97 | export function getElemBuf(elem: Buffer, start: number, length: number): Buffer { 98 | return elem.slice(32 - start - length, 32 - start); 99 | } 100 | 101 | /** 102 | * Set the most significant byte to 0 (in big endian) 103 | * @param {Buffer} elem - elem in big endian 104 | * @returns {Buffer} hash with the first byte set to 0 105 | */ 106 | export function clearElemMostSignificantByte(elem: Buffer): Buffer { 107 | return elem.fill(0, 0, 1); 108 | } 109 | 110 | /** 111 | * Check element in big endian must be less than claim element field 112 | * @param {Buffer} elem - elem in big endian 113 | * @throws {Error} throws an error when the check fails 114 | */ 115 | export function checkElemFitsClaim(elem: Buffer) { 116 | if (elem.length !== 32) { 117 | throw new Error('Element is not 32 bytes length'); 118 | } 119 | const elemBigInt = utils.bufferToBigIntBE(elem); 120 | if (elemBigInt.greater(babyJub.p)) { 121 | throw new Error('Element does not fit on claim element size'); 122 | } 123 | } 124 | 125 | /** 126 | * Check element in big endian must be length size specified 127 | * @param {Buffer} elem - elem in big endian 128 | * @param {Number} len - length that elem should be 129 | * @throws {Error} throws an error when the check fails 130 | */ 131 | export function checkByteLen(elem: Buffer, len: number) { 132 | if (elem.length !== len) { 133 | throw new Error(`Element is not ${len} bytes length`); 134 | } 135 | } 136 | 137 | /** 138 | * Set the claim type and version of an entry 139 | * @param {Object} entry - Entry of the claim 140 | * @param {number} claimType 141 | * @param {number} version 142 | */ 143 | export function setClaimTypeVersion(entry: Entry, claimType: number, version: number) { 144 | const claimTypeBuf = utils.bigIntToBufferBE(bigInt(claimType)).slice(24, 32); 145 | copyToElemBuf(entry.elements[3], 0, claimTypeBuf); 146 | copyToElemBuf(entry.elements[3], 8, num2buf(version)); 147 | } 148 | 149 | /** 150 | * get the claim type and version of an entry 151 | * @param {Object} entry - Entry of the claim 152 | * @returns {Object} type and version 153 | */ 154 | export function getClaimTypeVersion(entry: Entry): { claimType: number, version: number } { 155 | return { 156 | claimType: buf2num(getElemBuf(entry.elements[3], 0, 8)), 157 | version: buf2num(getElemBuf(entry.elements[3], 8, 4)), 158 | }; 159 | } 160 | 161 | /** 162 | * Increase `version` data field by 1 163 | * @param {Entry} claim - Claim to increase its version value 164 | */ 165 | export function incClaimVersion(claim: Entry) { 166 | const version = claim.elements[3].slice(20, 24).readUInt32BE(0); 167 | claim.elements[3].writeUInt32BE(version + 1, claim.elements[3].length - 64 / 8 - 32 / 8); 168 | } 169 | -------------------------------------------------------------------------------- /src/claim/claim-utils.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const snarkjs = require('snarkjs'); 7 | const { babyJub } = require('circomlib'); 8 | const claimUtils = require('./claim-utils'); 9 | const claim = require('./claim'); 10 | const utils = require('../utils'); 11 | 12 | const { bigInt } = snarkjs; 13 | const { expect } = chai; 14 | 15 | describe('[claim-utils]', () => { 16 | it('Increment claim version', () => { 17 | // hardcode entry 18 | const versionHardcoded = 1; 19 | const entryHex = '0x0000000000000000000000000000000000000000000000000000000000000000' 20 | + '0000000000000000000000000000000000000000000000000000000000000000' 21 | + '00036d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd' 22 | + '0000000000000000000000000000000000004d59000000010000000000000004'; 23 | const entry = Entry.newFromHex(entryHex); 24 | // increase version 25 | claimUtils.incClaimVersion(entry); 26 | const claimExample = claim.newClaimFromEntry(entry); 27 | expect(claimExample).to.be.not.equal(undefined); 28 | if (claimExample == null) { return; } 29 | const versionInc = claimExample.version; 30 | expect(versionInc).to.be.equal(versionHardcoded + 1); 31 | }); 32 | 33 | it('Clear most significant byte', () => { 34 | const testIn = '0xf6f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 35 | const testOutBuff = claimUtils.clearElemMostSignificantByte(utils.hexToBytes(testIn)); 36 | const testInSim = '0x00f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 37 | expect(testInSim).to.be.equal(utils.bytesToHex(testOutBuff)); 38 | 39 | const testIn2 = '0x00f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 40 | const testOutBuff2 = claimUtils.clearElemMostSignificantByte(utils.hexToBytes(testIn2)); 41 | expect(testIn2).to.be.equal(utils.bytesToHex(testOutBuff2)); 42 | }); 43 | 44 | it('Check valid entry claim', () => { 45 | // Check hash is more than 32 bytes 46 | const hash0 = '0xf6a4f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 47 | expect(() => { 48 | claimUtils.checkElemFitsClaim(utils.hexToBytes(hash0)); 49 | }).to.throw('Element is not 32 bytes length'); 50 | // Check hash is not valid for claim field 51 | const hash1 = '0xf6f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 52 | expect(() => { 53 | claimUtils.checkElemFitsClaim(utils.hexToBytes(hash1)); 54 | }).to.throw('Element does not fit on claim element size'); 55 | // Check hash is valid 56 | const hash2 = '0x06f36d94c84a7096c572b83d44df576e1ffb3573123f62099f8d4fa19de806bd'; 57 | expect(() => { 58 | claimUtils.checkElemFitsClaim(utils.hexToBytes(hash2)); 59 | }).not.to.throw(); 60 | }); 61 | 62 | it('Check valid entry element', () => { 63 | // Check elem fits on claim element size 64 | const test = babyJub.p; 65 | const test1 = test.add(bigInt(1)); 66 | expect(() => { 67 | claimUtils.checkElemFitsClaim(utils.bigIntToBufferBE(test)); 68 | }).not.to.throw(); 69 | expect(() => { 70 | claimUtils.checkElemFitsClaim(utils.bigIntToBufferBE(test1)); 71 | }).to.throw('Element does not fit on claim element size'); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/claim/claim.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Entry } from './entry'; 3 | import { AssignName } from './claim-assign-name'; 4 | import { AuthorizeKSignSecp256k1 } from './claim-authorize-ksign-secp256k1'; 5 | import { AuthorizeKSignBabyJub } from './claim-authorize-ksign-babyjub'; 6 | import { LinkObjectIdentity, TYPE_OBJECT } from './claim-link-object-identity'; 7 | import { Basic } from './claim-basic'; 8 | import { SetRootKey } from './claim-set-root-key'; 9 | import { AuthorizeEthKey, ETH_KEY_TYPE } from './claim-authorize-eth-key'; 10 | 11 | const claimUtils = require('./claim-utils'); 12 | 13 | export { 14 | Entry, 15 | AssignName, 16 | AuthorizeKSignSecp256k1, 17 | AuthorizeKSignBabyJub, 18 | LinkObjectIdentity, 19 | TYPE_OBJECT, 20 | Basic, 21 | SetRootKey, 22 | AuthorizeEthKey, 23 | ETH_KEY_TYPE, 24 | claimUtils, 25 | }; 26 | 27 | /** 28 | * Decode entry class into claim data structure depending on its type 29 | * @param {Object} entry - Claim element structure 30 | * @returns {Object} Claim raw data 31 | */ 32 | // eslint-disable-next-line max-len 33 | export function newClaimFromEntry(entry: Entry): void | Basic | AuthorizeKSignBabyJub | SetRootKey | AssignName | AuthorizeKSignSecp256k1 | LinkObjectIdentity | AuthorizeEthKey { 34 | // Decode claim type from Entry class 35 | const { claimType } = claimUtils.getClaimTypeVersion(entry); 36 | // Parse elements and return the proper claim structure 37 | switch (claimType) { 38 | case claimUtils.CLAIMTYPES.BASIC.TYPE: 39 | return Basic.newFromEntry(entry); 40 | case claimUtils.CLAIMTYPES.AUTHORIZE_KSIGN_BABYJUB.TYPE: 41 | return AuthorizeKSignBabyJub.newFromEntry(entry); 42 | case claimUtils.CLAIMTYPES.SET_ROOT_KEY.TYPE: 43 | return SetRootKey.newFromEntry(entry); 44 | case claimUtils.CLAIMTYPES.ASSIGN_NAME.TYPE: 45 | return AssignName.newFromEntry(entry); 46 | case claimUtils.CLAIMTYPES.AUTHORIZE_KSIGN_SECP256K1.TYPE: 47 | return AuthorizeKSignSecp256k1.newFromEntry(entry); 48 | case claimUtils.CLAIMTYPES.LINK_OBJECT_IDENTITY.TYPE: 49 | return LinkObjectIdentity.newFromEntry(entry); 50 | case claimUtils.CLAIMTYPES.AUTHORIZE_ETH_KEY.TYPE: 51 | return AuthorizeEthKey.newFromEntry(entry); 52 | default: 53 | throw new Error(`Unknown claim type ${claimType}`); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/claim/entry.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { getArrayBigIntFromBuffArrayBE, bigIntToBufferBE } from '../utils'; 3 | 4 | const { babyJub } = require('circomlib'); 5 | const snarkjs = require('snarkjs'); 6 | const { poseidon } = require('../crypto/crypto.js'); 7 | const utils = require('../utils'); 8 | 9 | const { bigInt } = snarkjs; 10 | 11 | const entryElemsLen = 4; 12 | 13 | // TODO: Reorganize claim-utils to avoid cycle dependencies 14 | /** 15 | * Check element in big endian must be less than claim element field 16 | * @param {Buffer} elem - elem in big endian 17 | * @throws {Error} throws an error when the check fails 18 | */ 19 | function checkElemFitsClaim(elem: Buffer) { 20 | if (elem.length !== 32) { 21 | throw new Error('Element is not 32 bytes length'); 22 | } 23 | const elemBigInt = utils.bufferToBigIntBE(elem); 24 | if (elemBigInt.greater(babyJub.p)) { 25 | throw new Error('Element does not fit on claim element size'); 26 | } 27 | } 28 | 29 | /** 30 | * Generic representation of claim elements 31 | * Entry element structure is as follows: |element 0|element 1|element 2|element 3| 32 | * Each element contains 253 useful bits enclosed on a 256 bits Buffer 33 | */ 34 | export class Entry { 35 | elements: Array; 36 | 37 | constructor(e0: Buffer, e1: Buffer, e2: Buffer, e3: Buffer) { 38 | checkElemFitsClaim(e0); 39 | checkElemFitsClaim(e1); 40 | checkElemFitsClaim(e2); 41 | checkElemFitsClaim(e3); 42 | this.elements = [e0, e1, e2, e3]; 43 | } 44 | 45 | /** 46 | * Initialize claim elements with empty buffer 47 | */ 48 | static newEmpty(): Entry { 49 | return new Entry(Buffer.alloc(32), Buffer.alloc(32), Buffer.alloc(32), Buffer.alloc(32)); 50 | } 51 | 52 | /** 53 | * Initialize claim elements from big ints 54 | */ 55 | static newFromBigInts(e0: bigInt, e1: bigInt, e2: bigInt, e3: bigInt): Entry { 56 | return new Entry( 57 | utils.bigIntToBufferBE(e0), 58 | utils.bigIntToBufferBE(e1), 59 | utils.bigIntToBufferBE(e2), 60 | utils.bigIntToBufferBE(e3), 61 | ); 62 | } 63 | 64 | /** 65 | * String deserialization into entry element structure 66 | * @param {String} Hexadecimal string representation of element claim structure 67 | */ 68 | static newFromHex(entryHex: string): Entry { 69 | const entryBuff = utils.hexToBytes(entryHex); 70 | const elements: any = [null, null, null, null]; 71 | for (let i = 0; i < entryElemsLen; i++) { 72 | // Slice buffer into 32 bytes to insert it into an specific element 73 | elements[(entryElemsLen - 1) - i] = entryBuff.slice(entryBuff.length - (32 * (i + 1)), entryBuff.length - 32 * i); 74 | } 75 | return new Entry(elements[0], elements[1], elements[2], elements[3]); 76 | } 77 | 78 | /** 79 | * Hash index calculation using poseidon hash 80 | * Hash index is calculated from: |element 1|element 0| 81 | * @returns {bigInt} Hash index of the claim element structure 82 | */ 83 | hiBigInt(): bigInt { 84 | const hashArray = [this.elements[2], this.elements[3]]; 85 | // return mimc7.multiHash(getArrayBigIntFromBuffArrayBE(hashArray)); 86 | return poseidon.multiHash(getArrayBigIntFromBuffArrayBE(hashArray)); 87 | } 88 | 89 | /** 90 | * Hash index calculation using poseidon hash 91 | * Hash index is calculated from: |element 1|element 0| 92 | * @returns {Buffer} Hash index of the claim element structure 93 | */ 94 | hi(): Buffer { 95 | return bigIntToBufferBE(this.hiBigInt()); 96 | } 97 | 98 | /** 99 | * Hash value calculation using poseidon hash 100 | * Hash value is calculated from: |element 3|element 2| 101 | * @returns {bigInt} Hash value of the claim element structure 102 | */ 103 | hvBigInt(): bigInt { 104 | const hashArray = [this.elements[0], this.elements[1]]; 105 | // return mimc7.multiHash(getArrayBigIntFromBuffArrayBE(hashArray)); 106 | return poseidon.multiHash(getArrayBigIntFromBuffArrayBE(hashArray)); 107 | } 108 | 109 | /** 110 | * Hash value calculation using poseidon hash 111 | * Hash value is calculated from: |element 3|element 2| 112 | * @returns {Buffer} Hash value of the claim element structure 113 | */ 114 | hv(): Buffer { 115 | return bigIntToBufferBE(this.hvBigInt()); 116 | } 117 | 118 | /** 119 | * Concats all the elements of the entry and parse it into an hexadecimal string 120 | * @returns {String} Hexadecimal string representation of element claim structure 121 | */ 122 | toHex(): string { 123 | return utils.bytesToHex(Buffer.concat(this.elements)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/claim/entry.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it, before } from 'mocha'; 3 | import { Entry } from './entry'; 4 | 5 | const chai = require('chai'); 6 | const utils = require('../utils'); 7 | 8 | const { expect } = chai; 9 | 10 | describe('[Entry]', () => { 11 | let entry; 12 | before('Create new entry', () => { 13 | entry = Entry.newEmpty(); 14 | }); 15 | 16 | it('Entry initial values', () => { 17 | expect(utils.bytesToHex(entry.elements[0])).to.be.equal(utils.bytesToHex(Buffer.alloc(32))); 18 | expect(utils.bytesToHex(entry.elements[1])).to.be.equal(utils.bytesToHex(Buffer.alloc(32))); 19 | expect(utils.bytesToHex(entry.elements[2])).to.be.equal(utils.bytesToHex(Buffer.alloc(32))); 20 | expect(utils.bytesToHex(entry.elements[3])).to.be.equal(utils.bytesToHex(Buffer.alloc(32))); 21 | }); 22 | it('Set values to entry elements', () => { 23 | entry.elements[0] = Buffer.alloc(32, 0); 24 | entry.elements[1] = Buffer.alloc(32, 1); 25 | entry.elements[2] = Buffer.alloc(32, 2); 26 | entry.elements[3] = Buffer.alloc(32, 3); 27 | expect(utils.bytesToHex(entry.elements[0])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 0))); 28 | expect(utils.bytesToHex(entry.elements[1])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 1))); 29 | expect(utils.bytesToHex(entry.elements[2])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 2))); 30 | expect(utils.bytesToHex(entry.elements[3])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 3))); 31 | }); 32 | it('Get Hash index', () => { 33 | const hi = entry.hi(); 34 | expect(utils.bytesToHex(hi)).to.be.equal('0x02bf80913bf94edbfdc660795e20588cb91e967c00ccd4078997d7bf61397e40'); 35 | }); 36 | it('Get Hash value', () => { 37 | const hv = entry.hv(); 38 | expect(utils.bytesToHex(hv)).to.be.equal('0x29c5498513405b5a57c474bc5e13889cc23a7739857dd7be1768f188ecd88a84'); 39 | }); 40 | it('Get hexadecimal from entry', () => { 41 | const entryHex = entry.toHex(); 42 | expect(entryHex).to.be.equal('0x0000000000000000000000000000000000000000000000000000000000000000' 43 | + '0101010101010101010101010101010101010101010101010101010101010101' 44 | + '0202020202020202020202020202020202020202020202020202020202020202' 45 | + '0303030303030303030303030303030303030303030303030303030303030303'); 46 | }); 47 | it('Get entry from hexadecimal', () => { 48 | const entryHex = '0x0000000000000000000000000000000000000000000000000000000000000000' 49 | + '0101010101010101010101010101010101010101010101010101010101010101' 50 | + '0202020202020202020202020202020202020202020202020202020202020202' 51 | + '0303030303030303030303030303030303030303030303030303030303030303'; 52 | entry = Entry.newFromHex(entryHex); 53 | expect(utils.bytesToHex(entry.elements[0])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 0))); 54 | expect(utils.bytesToHex(entry.elements[1])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 1))); 55 | expect(utils.bytesToHex(entry.elements[2])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 2))); 56 | expect(utils.bytesToHex(entry.elements[3])).to.be.equal(utils.bytesToHex(Buffer.alloc(32, 3))); 57 | }); 58 | it('.fromHex equivalent to .toHex', () => { 59 | const originalLeafHex = '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c2e48632c87932663beff7a1f6deb692cc61b041262ae8f310203d0f5ff50000000000000000000000000000000000007833000000000000000000000004'; 60 | const leaf = Entry.newFromHex(originalLeafHex); 61 | leaf.hi(); 62 | leaf.hv(); 63 | expect(leaf.toHex()).to.be.equal(originalLeafHex); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | const DBPREFIX = 'i3db-'; 4 | const KCPREFIX = 'i3kc'; 5 | const IDPREFIX = 'id-'; 6 | const KEYPREFIX = 'keys-'; 7 | const IDRECOVERYPREFIX = 'idRecovery'; 8 | const PUBKEYBACKUP = 'pubKeyBackup'; 9 | const CLAIMPREFIX = 'claim-'; 10 | const MTPREFIX = 'i3mt-'; 11 | 12 | const NAMESPACEHASH = utils.hashBytes(Buffer.from('iden3.io')); 13 | 14 | module.exports = { 15 | PUBKEYBACKUP, 16 | IDRECOVERYPREFIX, 17 | IDPREFIX, 18 | KEYPREFIX, 19 | CLAIMPREFIX, 20 | DBPREFIX, 21 | KCPREFIX, 22 | MTPREFIX, 23 | NAMESPACEHASH, 24 | }; 25 | -------------------------------------------------------------------------------- /src/crypto/babyjub-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const { babyJub, eddsa } = require('circomlib'); 4 | const crypto = require('crypto'); 5 | const { bigInt } = require('snarkjs'); 6 | const createBlakeHash = require('blake-hash'); 7 | 8 | const utils = require('../utils'); 9 | 10 | 11 | const baseBabyJub = babyJub.Base8; 12 | 13 | /** 14 | * Get compressed point given a public key compsed by coordinate X and Y 15 | * @param {Buffer} pubKeyX - Coordinate X of public key 16 | * @param {Buffer} pubKeyY - Coordinate Y of public key 17 | * @returns {Buffer} - Public key compressed 18 | */ 19 | function compressPoint(pubKeyX: Buffer, pubKeyY: Buffer): Buffer { 20 | const pubKeyXBigInt = utils.bufferToBigIntBE(pubKeyX); 21 | if (pubKeyXBigInt.greater(babyJub.p.shr(1))) { 22 | pubKeyY[0] |= 0x80; 23 | } 24 | return pubKeyY; 25 | } 26 | 27 | /** 28 | * Get number of bits given a big integer 29 | * @param {bigInt} number - big integer 30 | * @returns {number} - number of bits necessary to represent big integer input 31 | */ 32 | function bigIntbits(number: bigInt): number { 33 | let numBits = 0; 34 | while (!number.isZero()) { 35 | number = number.shr(1); 36 | numBits += 1; 37 | } 38 | return numBits; 39 | } 40 | 41 | /** 42 | * Generates a random private key in a subgroup specified by the babyjub field 43 | * @returns {string} - Hexadecimal string 44 | */ 45 | function genPriv(): string { 46 | const randBytes = crypto.randomBytes(Math.floor(256 / 8)); 47 | const randHex = utils.bytesToHex(randBytes); 48 | return randHex; 49 | } 50 | 51 | /** 52 | * Retrieve uniform scalar in babyjub curve subgroup 53 | * @param {Buffer} privKey - Private key 54 | * @returns {bigInt} scalar in subgroup babyjub order 55 | */ 56 | function privToScalar(privKey: Buffer): bigInt { 57 | const h1 = createBlakeHash('blake512').update(privKey).digest(); 58 | const sBuff = eddsa.pruneBuffer(h1.slice(0, 32)); 59 | const scalar = (bigInt.leBuff2int(sBuff)).shr(3); 60 | if (scalar >= babyJub.p) { 61 | throw new Error('scalar generated larger than subgroup'); 62 | } 63 | return scalar; 64 | } 65 | 66 | /** 67 | * Retrieve public key from private key in a babyjub curve 68 | * @param {Buffer} privKey - Private key 69 | * @param {bool} compress - Flag to indicate if output is public key compresed or not 70 | * @returns {Buffer} New public key generated 71 | */ 72 | function privToPub(privKey: Buffer, compress: boolean): Buffer { 73 | if (privKey.length !== 32) { 74 | throw new Error(`Input Error: Buffer has ${privKey.length} bytes. It should be 32 bytes`); 75 | } 76 | const scalar = privToScalar(privKey); 77 | const pubKey = babyJub.mulPointEscalar(baseBabyJub, scalar); 78 | const pubKeyX = utils.bigIntToBufferBE(pubKey[0]); 79 | const pubKeyY = utils.bigIntToBufferBE(pubKey[1]); 80 | if (!babyJub.inSubgroup(pubKey)) { 81 | throw new Error('Point generated not in babyjub subgroup'); 82 | } 83 | if (!compress) { 84 | return Buffer.concat([pubKeyX, pubKeyY]); 85 | } 86 | return compressPoint(pubKeyX, pubKeyY); 87 | } 88 | 89 | module.exports = { 90 | privToScalar, 91 | privToPub, 92 | genPriv, 93 | bigIntbits, 94 | compressPoint, 95 | }; 96 | -------------------------------------------------------------------------------- /src/crypto/babyjub-utils.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it } from 'mocha'; 3 | 4 | const chai = require('chai'); 5 | const babyUtils = require('./babyjub-utils.js'); 6 | const utils = require('../utils'); 7 | 8 | const { expect } = chai; 9 | 10 | describe('[bayjub utils]', () => { 11 | const privKey = '0x28156abe7fe2fd433dc9df969286b96666489bac508612d0e16593e944c4f69f'; 12 | const privKeyBuff = utils.hexToBytes(privKey); 13 | 14 | it('Generate public key uncompressed', () => { 15 | const pubKey = babyUtils.privToPub(privKeyBuff, false); 16 | expect(utils.bytesToHex(pubKey)).to.be.equal('0x270000b73fba5f79c0491a32d4e64f69813db369ea106c09bc5ca4ae220cbb81' 17 | + '2d9e82263b94a343ee95d56c810a5a0adb63a439cd5b4944dfb56f09e28c6f04'); 18 | }); 19 | 20 | it('Generate public key compressed', () => { 21 | const pubKeyCompressed = babyUtils.privToPub(privKeyBuff, true); 22 | expect(utils.bytesToHex(pubKeyCompressed)).to.be.equal('0xad9e82263b94a343ee95d56c810a5a0adb63a439cd5b4944dfb56f09e28c6f04'); 23 | }); 24 | 25 | it('Generate public key from private key different than 32 bytes', () => { 26 | const privKey31 = Buffer.alloc(31).fill('A'); 27 | const privKey33 = Buffer.alloc(33).fill('B'); 28 | expect(() => { babyUtils.privToPub(privKey31, true); }).to.throw('Input Error: Buffer has 31 bytes. It should be 32 bytes'); 29 | expect(() => { babyUtils.privToPub(privKey33, true); }).to.throw('Input Error: Buffer has 33 bytes. It should be 32 bytes'); 30 | }); 31 | 32 | it('Generate random private key, retrieve public', () => { 33 | const randPrivKeyHex = babyUtils.genPriv(); 34 | const randPrivKeyBuff = utils.hexToBytes(randPrivKeyHex); 35 | expect(() => { babyUtils.privToPub(randPrivKeyBuff, true); }).not.to.throw(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/crypto/crypto.js: -------------------------------------------------------------------------------- 1 | const utilsBabyJub = require('./babyjub-utils'); 2 | const eddsaBabyJub = require('./eddsa-babyjub'); 3 | const mimc7 = require('./mimc7'); 4 | const poseidon = require('./poseidon'); 5 | 6 | module.exports = { 7 | utilsBabyJub, 8 | eddsaBabyJub, 9 | mimc7, 10 | poseidon, 11 | }; 12 | -------------------------------------------------------------------------------- /src/crypto/eddsa-babyjub.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const crypto = require('crypto'); 4 | const createBlakeHash = require('blake-hash'); 5 | const { babyJub, eddsa } = require('circomlib'); 6 | const { bigInt } = require('snarkjs'); 7 | 8 | 9 | /** 10 | * Class representing EdDSA Baby Jub signature 11 | */ 12 | export class Signature { 13 | r8: [bigInt, bigInt]; 14 | s: bigInt; 15 | 16 | /** 17 | * Create a Signature with the R8 point and S scalar 18 | * @param {Array[bigInt]} r8 - R8 point 19 | * @param {bigInt} s - Scalar 20 | */ 21 | constructor(r8: [bigInt, bigInt], s: bigInt) { 22 | this.r8 = r8; 23 | this.s = s; 24 | } 25 | 26 | /** 27 | * Create a Signature from a compressed Signature Buffer 28 | * @param {Buffer} buf - Buffer containing a signature 29 | * @returns {Signature} Object signature 30 | */ 31 | static newFromCompressed(buf: Buffer): Signature { 32 | if (buf.length !== 64) { 33 | throw new Error('buf must be 64 bytes'); 34 | } 35 | const sig = eddsa.unpackSignature(buf); 36 | if (sig.R8 == null) { 37 | throw new Error('unpackSignature failed'); 38 | } 39 | return new Signature(sig.R8, sig.S); 40 | } 41 | 42 | /** 43 | * Take the signature and pack it into a buffer 44 | * @returns {Buffer} - Signature compressed 45 | */ 46 | compress(): Buffer { 47 | return eddsa.packSignature({ R8: this.r8, S: this.s }); 48 | } 49 | 50 | /** 51 | * Take the signature and pack it into an hex encoding 52 | * @returns {string} - hex encoding of the signature 53 | */ 54 | toString(): string { 55 | return this.compress().toString('hex'); 56 | } 57 | } 58 | 59 | /** 60 | * Class representing a EdDSA baby jub public key 61 | */ 62 | export class PublicKey { 63 | p: [bigInt, bigInt]; 64 | 65 | /** 66 | * Create a PublicKey from a curve point p 67 | * @param {Array[bigInt]} p - curve point 68 | */ 69 | constructor(p: [bigInt, bigInt]) { 70 | this.p = p; 71 | } 72 | 73 | /** 74 | * Create a PublicKey from a compressed PublicKey Buffer 75 | * @param {Buffer} buff - compressed public key in a buffer 76 | * @returns {PublicKey} public key class 77 | */ 78 | static newFromCompressed(buf: Buffer): PublicKey { 79 | if (buf.length !== 32) { 80 | throw new Error('buf must be 32 bytes'); 81 | } 82 | // const bufLE = utils.swapEndianness(buf); 83 | const p = babyJub.unpackPoint(buf); 84 | if (p == null) { 85 | throw new Error('unpackPoint failed'); 86 | } 87 | return new PublicKey(p); 88 | } 89 | 90 | /** 91 | * Compress the PublicKey 92 | * @returns {Buffer} - point compressed into a buffer 93 | */ 94 | compress(): Buffer { 95 | // return utils.swapEndianness(babyJub.packPoint(this.p)); 96 | return babyJub.packPoint(this.p); 97 | } 98 | 99 | /** 100 | * Compress the PublicKey 101 | * @returns {string} - hex encoding of the compressed public key 102 | */ 103 | toString(): string { 104 | return this.compress().toString('hex'); 105 | } 106 | 107 | /** 108 | * Verify the signature of a bigInt message using mimc7 hash 109 | * @param {bigInt} msg - message to verify 110 | * @param {Signature} sig - signature to check 111 | * @returns {boolean} True if validation is succesfull; otherwise false 112 | */ 113 | verifyMimc7(msg: bigInt, sig: Signature): boolean { 114 | return eddsa.verifyMiMC(msg, { R8: sig.r8, S: sig.s }, this.p); 115 | } 116 | 117 | /** 118 | * Verify the signature of a bigInt message using Poseidon hash 119 | * @param {bigInt} msg - message to verify 120 | * @param {Signature} sig - signature to check 121 | * @returns {boolean} True if validation is succesfull; otherwise false 122 | */ 123 | verifyPoseidon(msg: bigInt, sig: Signature): boolean { 124 | return eddsa.verifyPoseidon(msg, { R8: sig.r8, S: sig.s }, this.p); 125 | } 126 | } 127 | 128 | /** 129 | * Class representing EdDSA Baby Jub private key 130 | */ 131 | export class PrivateKey { 132 | sk: Buffer; 133 | 134 | /** 135 | * Create a PirvateKey from a 32 byte Buffer 136 | * @param {Buffer} buf - private key 137 | */ 138 | constructor(buf: Buffer) { 139 | if (buf.length !== 32) { 140 | throw new Error('buf must be 32 bytes'); 141 | } 142 | this.sk = buf; 143 | } 144 | 145 | /** 146 | * Create a random PrivateKey 147 | * @returns {PrivateKey} PrivateKey class created from a random private key 148 | */ 149 | static newRandom(): PrivateKey { 150 | const buf = crypto.randomBytes(Math.floor(256 / 8)); 151 | return new PrivateKey(buf); 152 | } 153 | 154 | /** 155 | * Return the PrivateKey in hex encoding 156 | * @returns {string} hex string representing the private key 157 | */ 158 | toString(): string { 159 | return this.sk.toString('hex'); 160 | } 161 | 162 | /** 163 | * Retrieve PublicKey of the PrivateKey 164 | * @returns {PublicKey} PublicKey derived from PrivateKey 165 | */ 166 | public(): PublicKey { 167 | return new PublicKey(eddsa.prv2pub(this.sk)); 168 | } 169 | 170 | /** 171 | * Retrieve private scalar of the PrivateKey 172 | * @returns {bigInt} Prvate scalar derived from PrivateKey 173 | */ 174 | toPrivScalar(): bigInt { 175 | const h1 = createBlakeHash('blake512').update(this.sk).digest(); 176 | const sBuff = eddsa.pruneBuffer(h1.slice(0, 32)); 177 | return (bigInt.leBuff2int(sBuff)).shr(3); 178 | } 179 | 180 | /** 181 | * Sign a bigInt message using mimc7 hash 182 | * @param {bigInt} msg - message to sign 183 | * @returns {Signature} Signature generated 184 | */ 185 | signMimc7(msg: bigInt): Signature { 186 | const s = eddsa.signMiMC(this.sk, msg); 187 | return new Signature(s.R8, s.S); 188 | } 189 | 190 | /** 191 | * Sign a bigInt message using Poseidon hash 192 | * @param {bigInt} msg - message to sign 193 | * @returns {Signature} Signature generated 194 | */ 195 | signPoseidon(msg: bigInt): Signature { 196 | const s = eddsa.signPoseidon(this.sk, msg); 197 | return new Signature(s.R8, s.S); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/crypto/eddsa-babyjub.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { describe, it } from 'mocha'; 4 | 5 | const chai = require('chai'); 6 | const { bigInt } = require('snarkjs'); 7 | const eddsa = require('./eddsa-babyjub.js'); 8 | const utils = require('../utils'); 9 | const utilsBabyJub = require('./babyjub-utils.js'); 10 | 11 | const { expect } = chai; 12 | 13 | describe('[eddsa babyjyb]', () => { 14 | const skHex = '0001020304050607080900010203040506070809000102030405060708090001'; 15 | const sk = new eddsa.PrivateKey(utils.hexToBytes(skHex)); 16 | 17 | const msgHex = '00010203040506070809'; 18 | const msgBuff = utils.hexToBytes(msgHex); 19 | 20 | const msg = bigInt.leBuff2int(msgBuff); 21 | 22 | const pk = sk.public(); 23 | const sig = sk.signMimc7(msg); 24 | 25 | it('pk.x', () => { 26 | expect(pk.p[0].toString()).to.be.equal('13277427435165878497778222415993513565335242147425444199013288855685581939618'); 27 | }); 28 | 29 | it('pk.y', () => { 30 | expect(pk.p[1].toString()).to.be.equal('13622229784656158136036771217484571176836296686641868549125388198837476602820'); 31 | }); 32 | 33 | it('sign', () => { 34 | // expect(.toString()).to.be.equal(''); 35 | expect(sig.r8[0].toString()).to.be.equal('11384336176656855268977457483345535180380036354188103142384839473266348197733'); 36 | expect(sig.r8[1].toString()).to.be.equal('15383486972088797283337779941324724402501462225528836549661220478783371668959'); 37 | expect(sig.s.toString()).to.be.equal('2523202440825208709475937830811065542425109372212752003460238913256192595070'); 38 | }); 39 | 40 | it('verify', () => { 41 | expect(pk.verifyMimc7(msg, sig)).to.be.equal(true); 42 | }); 43 | 44 | it('compress & decompress', () => { 45 | const sigComp = sig.compress(); 46 | expect(sigComp.toString('hex')).to.be.equal('' 47 | + 'dfedb4315d3f2eb4de2d3c510d7a987dcab67089c8ace06308827bf5bcbe02a2' 48 | + '7ed40dab29bf993c928e789d007387998901a24913d44fddb64b1f21fc149405'); 49 | const sig2 = eddsa.Signature.newFromCompressed(sigComp); 50 | expect(pk.verifyMimc7(msg, sig2)).to.be.equal(true); 51 | }); 52 | 53 | it('scalar', () => { 54 | const privKeyBuff = utils.hexToBytes(skHex); 55 | const scalarPriv1 = utilsBabyJub.privToScalar(privKeyBuff); 56 | const scalarPriv2 = sk.toPrivScalar(); 57 | expect(scalarPriv1.toString()).to.be.equal(scalarPriv2.toString()); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/crypto/mimc7.js: -------------------------------------------------------------------------------- 1 | const { bigInt } = require('snarkjs'); 2 | const { mimc7 } = require('circomlib'); 3 | 4 | function hash(arr) { 5 | return mimc7.hash(arr[0], arr[1]); 6 | } 7 | 8 | function multiHash(arr) { 9 | // TODO check bigints inside finite field 10 | return mimc7.multiHash(arr); 11 | } 12 | 13 | function hashBuffer(msg) { // msg is a Buffer 14 | const n = 31; 15 | const msgArray = []; 16 | const fullParts = Math.floor(msg.length / n); 17 | for (let i = 0; i < fullParts; i++) { 18 | const v = bigInt.leBuff2int(msg.slice(n * i, n * (i + 1))); 19 | msgArray.push(v); 20 | } 21 | if (msg.length % n !== 0) { 22 | const v = bigInt.leBuff2int(msg.slice(fullParts * n)); 23 | msgArray.push(v); 24 | } 25 | return mimc7.multiHash(msgArray); 26 | } 27 | 28 | module.exports = { 29 | hash, 30 | multiHash, 31 | hashBuffer, 32 | }; 33 | -------------------------------------------------------------------------------- /src/crypto/poseidon.js: -------------------------------------------------------------------------------- 1 | const { bigInt, bn128 } = require('snarkjs'); 2 | 3 | const F = bn128.Fr; 4 | // const Poseidon = require('../../node_modules/circomlib/src/poseidon.js'); 5 | const { poseidon } = require('circomlib'); 6 | 7 | function hash(arr) { 8 | const poseidonHash = poseidon.createHash(6, 8, 57); 9 | return poseidonHash(arr); 10 | } 11 | 12 | function multiHash(arr) { 13 | // TODO check bigints inside finite field 14 | 15 | let r = bigInt(0); 16 | for (let i = 0; i < arr.length; i += 5) { 17 | const fiveElems = []; 18 | for (let j = 0; j < 5; j++) { 19 | if (i + j < arr.length) { 20 | fiveElems.push(arr[i + j]); 21 | } else { 22 | fiveElems.push(bigInt(0)); 23 | } 24 | } 25 | const ph = hash(fiveElems); 26 | r = F.add(r, ph); 27 | } 28 | return F.affine(r); 29 | } 30 | 31 | function hashBuffer(msgBuff) { 32 | const n = 31; 33 | const msgArray = []; 34 | const fullParts = Math.floor(msgBuff.length / n); 35 | for (let i = 0; i < fullParts; i++) { 36 | const v = bigInt.leBuff2int(msgBuff.slice(n * i, n * (i + 1))); 37 | msgArray.push(v); 38 | } 39 | if (msgBuff.length % n !== 0) { 40 | const v = bigInt.leBuff2int(msgBuff.slice(fullParts * n)); 41 | msgArray.push(v); 42 | } 43 | return multiHash(msgArray); 44 | } 45 | 46 | module.exports = { 47 | hash, 48 | multiHash, 49 | hashBuffer, 50 | }; 51 | -------------------------------------------------------------------------------- /src/db/db.js: -------------------------------------------------------------------------------- 1 | const LocalStorage = require('./localstorage-db'); 2 | const Memory = require('./memory-db'); 3 | 4 | module.exports = { 5 | LocalStorage, 6 | Memory, 7 | }; 8 | -------------------------------------------------------------------------------- /src/db/localstorage-db.js: -------------------------------------------------------------------------------- 1 | const kcUtils = require('../key-container/kc-utils'); 2 | const CONSTANTS = require('../constants'); 3 | 4 | /** 5 | * @param {String} url - url of the backup system backend 6 | */ 7 | class Db { 8 | constructor(subprefix) { 9 | if (subprefix !== undefined) { 10 | this.prefix = CONSTANTS.DBPREFIX + subprefix; 11 | } else { 12 | this.prefix = CONSTANTS.DBPREFIX; 13 | } 14 | } 15 | 16 | /** 17 | * @param {String} key 18 | * @param {String} value 19 | */ 20 | insert(key, value) { 21 | localStorage.setItem(this.prefix + key, value); 22 | } 23 | 24 | /** 25 | * @param {String} key 26 | * @returns {String} 27 | */ 28 | get(key) { 29 | return localStorage.getItem(this.prefix + key); 30 | } 31 | 32 | /** 33 | * @param {String} key 34 | */ 35 | delete(key) { 36 | localStorage.removeItem(this.prefix + key); 37 | } 38 | 39 | deleteAll() { 40 | localStorage.clear(); 41 | } 42 | 43 | /** 44 | * Get all keys of the localStorage that match the given prefix 45 | * @param {String} prefix - Added to internal database prefix 46 | * @returns {Array} Contains all the keys found 47 | */ 48 | listKeys(prefix) { 49 | const keyList = []; 50 | const localStorageLength = localStorage.length; 51 | for (let i = 0, len = localStorageLength; i < len; i++) { 52 | // get only the stored data related to identities (that have the prefix) 53 | if (localStorage.key(i).indexOf(this.prefix + prefix) !== -1) { 54 | const key = localStorage.key(i); 55 | keyList.push(key.replace(this.prefix, '')); 56 | } 57 | } 58 | return keyList; 59 | } 60 | 61 | /** 62 | * Gets all the localStorage data related with the iden3js library, and packs it into an encrpyted string 63 | * @param {Object} kc - KeyContainer 64 | * @returns {Object} - encrypted packed data 65 | */ 66 | exportLocalStorage(kc) { 67 | if (!kc.encryptionKey) { 68 | // KeyContainer not unlocked 69 | console.error('Error: KeyContainer not unlocked'); 70 | return undefined; 71 | } 72 | const dbExp = {}; 73 | 74 | for (let i = 0; i < localStorage.length; i++) { 75 | // get only the stored data related to db (that have the prefix) 76 | if (localStorage.key(i).indexOf(this.prefix) !== -1) { 77 | dbExp[localStorage.key(i)] = localStorage.getItem(localStorage.key(i)); 78 | } 79 | } 80 | const dbExpStr = JSON.stringify(dbExp); 81 | return kcUtils.encrypt(kc.encryptionKey, dbExpStr); // encrypted database 82 | } 83 | 84 | /** 85 | * Decrypts the encrypted packed data and saves it into localStorage 86 | * @param {Object} kc - KeyContainer 87 | * @param {String} Database encrypted 88 | */ 89 | importLocalStorage(kc, dbEncrypted) { 90 | const dbExpStr = kcUtils.decrypt(kc.encryptionKey, dbEncrypted); 91 | const dbExp = JSON.parse(dbExpStr); 92 | 93 | Object.keys(dbExp).forEach((key) => { 94 | localStorage.setItem(key, dbExp[key]); 95 | }); 96 | } 97 | 98 | /** 99 | * Gets all the localStorage data and packs it into an encrpyted string 100 | * @param {Object} kc - KeyContainer 101 | * @returns {String} - Encrypted packed data 102 | */ 103 | exportWallet(kc) { 104 | try { 105 | const lsStr = JSON.stringify(localStorage); 106 | const pubBackupKey = kc.getBackupPubKey(); 107 | 108 | return kcUtils.encryptBox(pubBackupKey, lsStr); 109 | } catch (error) { 110 | return undefined; 111 | } 112 | } 113 | 114 | /** 115 | * Decrypts the encrypted database packed data and saves it into localStorage 116 | * @param {String} masterSeed - Mnemonic representing the master seed 117 | * @param {Object} kc - KeyContainer 118 | * @param {String} Database encrypted 119 | * @returns {Bool} - True if database is imported correctly, otherwise False 120 | */ 121 | importWallet(masterSeed, kc, dbEncrypted) { 122 | try { 123 | const keyPair = kc.getPrivKeyBackUpFromSeed(masterSeed); 124 | const dbExpStr = kcUtils.decryptBox(keyPair.privateKey, keyPair.publicKey, dbEncrypted); 125 | const dbExp = JSON.parse(dbExpStr); 126 | 127 | this.deleteAll(); 128 | Object.keys(dbExp).forEach((key) => { 129 | localStorage.setItem(key, dbExp[key]); 130 | }); 131 | return true; 132 | } catch (error) { 133 | return false; 134 | } 135 | } 136 | } 137 | 138 | module.exports = Db; 139 | -------------------------------------------------------------------------------- /src/db/localstorage-db.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Db = require('./db'); 3 | const KeyContainer = require('../key-container/key-container'); 4 | 5 | const { expect } = chai; 6 | 7 | describe('[Database] export and import database', () => { 8 | const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye'; 9 | let dataBase; 10 | let keyContainer; 11 | before('Create database and fill it', () => { 12 | dataBase = new Db.LocalStorage(); 13 | keyContainer = new KeyContainer(dataBase); 14 | for (let i = 0; i < 10; i++) { 15 | const key = `key-${i}`; 16 | const value = `value-${i}`; 17 | 18 | dataBase.insert(key, value); 19 | } 20 | }); 21 | 22 | before('Generate public key backup', () => { 23 | keyContainer.unlock('pass'); 24 | const publicBackupKey = keyContainer.generateKeyBackUp(mnemonic); 25 | expect(publicBackupKey).to.be.not.equal(undefined); 26 | keyContainer.lock(); 27 | }); 28 | 29 | after('lock', () => { 30 | keyContainer.lock(); 31 | }); 32 | 33 | it('Export and import database', () => { 34 | keyContainer.unlock('pass'); 35 | // Export wallet 36 | const lsEncrypted = dataBase.exportWallet(keyContainer); 37 | expect(lsEncrypted).to.be.not.equal(undefined); 38 | // Import wallet 39 | // Delete LocalStorage 40 | dataBase.deleteAll(); 41 | const ack = dataBase.importWallet(mnemonic, keyContainer, lsEncrypted); 42 | if (!ack) { 43 | throw new Error('Error importing database'); 44 | } 45 | for (let i = 0; i < 10; i++) { 46 | const key = `key-${i}`; 47 | const value = `value-${i}`; 48 | const importValue = dataBase.get(key); 49 | expect(importValue).to.be.equal(value); 50 | } 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/db/memory-db.js: -------------------------------------------------------------------------------- 1 | const CONSTANTS = require('../constants'); 2 | 3 | /** 4 | * Database interface for memory allocation 5 | */ 6 | class MemorydB { 7 | /** 8 | * @param {Bool} prefix - Database prefix 'i3db-' added to key values 9 | */ 10 | constructor(prefix = true) { 11 | this.prefix = prefix ? CONSTANTS.DBPREFIX : ''; 12 | this.db = new Map(); 13 | } 14 | 15 | /** 16 | * Method to store [key - value] on database 17 | * @param {String} key 18 | * @param {String} value 19 | */ 20 | insert(key, value) { 21 | this.db.set(this.prefix + key, value); 22 | } 23 | 24 | /** 25 | * Method to retrieve a value given a key 26 | * @param {String} key 27 | * @returns {String} 28 | */ 29 | get(key) { 30 | const value = this.db.get(this.prefix + key); 31 | if (value === undefined) { return null; } 32 | return value; 33 | } 34 | 35 | /** 36 | * Method to delete a value given a key 37 | * @param {String} key 38 | */ 39 | delete(key) { 40 | this.db.delete(this.prefix + key); 41 | } 42 | 43 | /** 44 | * Method to delete all the [key - value] items 45 | */ 46 | deleteAll() { 47 | this.db.clear(); 48 | } 49 | } 50 | 51 | module.exports = MemorydB; 52 | -------------------------------------------------------------------------------- /src/db/memory-db.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Db = require('./db'); 3 | const CONSTANTS = require('../constants'); 4 | 5 | const { expect } = chai; 6 | 7 | describe('[memory database]', () => { 8 | let dataBase; 9 | 10 | it('Check prefix', () => { 11 | const dataBaseNoPrefix = new Db.Memory(false); 12 | expect(dataBaseNoPrefix.prefix).to.be.equal(''); 13 | const dataBasePrefix = new Db.Memory(true); 14 | expect(dataBasePrefix.prefix).to.be.equal(CONSTANTS.DBPREFIX); 15 | }); 16 | 17 | it('Create database and fill it', () => { 18 | dataBase = new Db.Memory(); 19 | for (let i = 0; i < 10; i++) { 20 | const key = `key-${i}`; 21 | const value = `value-${i}`; 22 | dataBase.insert(key, value); 23 | } 24 | }); 25 | 26 | it('Get database values', () => { 27 | for (let i = 0; i < 10; i++) { 28 | const key = `key-${i}`; 29 | const value = dataBase.get(key); 30 | expect(value).to.be.equal(`value-${i}`); 31 | } 32 | }); 33 | 34 | it('Clear single key', () => { 35 | const singleKey = 'key-3'; 36 | dataBase.delete(singleKey); 37 | const value = dataBase.get(singleKey); 38 | expect(value).to.be.equal(null); 39 | }); 40 | 41 | it('Clear full database', () => { 42 | dataBase.deleteAll(); 43 | for (let i = 0; i < 10; i++) { 44 | const key = `key-${i}`; 45 | const value = dataBase.get(key); 46 | expect(value).to.be.equal(null); 47 | } 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/eth/counterfactual.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Web3 = require('web3'); 3 | 4 | const web3 = new Web3(); 5 | 6 | /** 7 | * Encode parameter to pass as contract argument 8 | * @param {String} path - Path of the json file 9 | * @returns {String} - bytecode 10 | */ 11 | function encodeParam(dataType, data) { 12 | return web3.eth.abi.encodeParameter(dataType, data); 13 | } 14 | 15 | /** 16 | * Converts an int to uint256 17 | * @param {Number} value 18 | * @returns {String} - uint256 hex string 19 | */ 20 | function numberToUint256(value) { 21 | const hex = value.toString(16); 22 | return `0x${'0'.repeat(64 - hex.length)}${hex}`; 23 | } 24 | 25 | /** 26 | * Read the bytecode from the contract json file 27 | * @param {String} path - Path of the json file 28 | * @returns {String} - bytecode 29 | */ 30 | function readContractFile(path) { 31 | const contentRaw = fs.readFileSync(path); 32 | const content = JSON.parse(contentRaw.toString()); 33 | return content.bytecode; 34 | } 35 | 36 | /** 37 | * Calculate counterfactual idAddr of the identity 38 | * Deterministically computes the smart contract address given 39 | * the account the will deploy the contract (factory contract) 40 | * the salt as uint256 and the contract bytecode 41 | * original from: https://github.com/miguelmota/solidity-create2-example 42 | * @param {String} creatorAddr - Eth address of the creator 43 | * @param {String} salt - Salt 44 | * @param {String} byteCode - Full bytecode concatenated with the constructor parameters of the smart contract 45 | * @returns {String} - idAddr, the computed eth address of the counterfactual contract 46 | */ 47 | function buildCreate2Address(creatorAddr, salt, byteCode) { 48 | return `0x${web3.utils.sha3(`0x${['ff', creatorAddr, numberToUint256(salt), web3.utils.sha3(byteCode)].map(x => x.replace(/0x/, '')).join('')}`).slice(-40)}`.toLowerCase(); 49 | } 50 | 51 | /** 52 | * Calculate counterfactual idAddr of the identity 53 | * @param {String} kop - Operational key 54 | * @param {String} krec - Recovery key 55 | * @param {String} krev - Revokator key 56 | * @param {String} relayAddr - Relay eth address 57 | * @param {String} iden3implAddr - Eth address of deployed IDen3Impl contract 58 | * @param {String} iden3deployerAddr - Eth address of deployed Deployer contract 59 | * @param {String} bytecode - Bytecode of the IDen3DelegateProxy contract 60 | * @returns {String} - idAddr, the computed eth address of the counterfactual contract 61 | */ 62 | function calculateIdAddress(kop, krec, krev, relayAddr, iden3implAddr, iden3deployerAddr, bytecode) { 63 | const bytecodefull = `${bytecode}${ 64 | encodeParam('address', kop).slice(2)}${ 65 | encodeParam('address', relayAddr).slice(2)}${ 66 | encodeParam('address', krec).slice(2)}${ 67 | encodeParam('address', krev).slice(2)}${ 68 | encodeParam('address', iden3implAddr).slice(2)}`; 69 | const salt = 0; 70 | return buildCreate2Address(iden3deployerAddr, salt, bytecodefull); 71 | } 72 | 73 | module.exports = { 74 | encodeParam, 75 | readContractFile, 76 | buildCreate2Address, 77 | calculateIdAddress, 78 | }; 79 | -------------------------------------------------------------------------------- /src/eth/counterfactual.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Web3 = require('web3'); 3 | const ethUtil = require('ethereumjs-util'); 4 | 5 | const { expect } = chai; 6 | const web3 = new Web3(); 7 | 8 | const iden3 = require('../index'); 9 | 10 | const privKHex = 'da7079f082a1ced80c5dee3bf00752fd67f75321a637e5d5073ce1489af062d8'; 11 | const privK = iden3.utils.hexToBytes(privKHex); 12 | const address = ethUtil.privateToAddress(privK); 13 | const addressHex = iden3.utils.bytesToHex(address); 14 | // const pubK = ethUtil.privateToPublic(privK); 15 | // const pubKHex = iden3.utils.bytesToHex(pubK); 16 | 17 | const iden3implAddr = '0x66D0c2F85F1B717168cbB508AfD1c46e07227130'; // address of deployed IDen3Impl contract 18 | const iden3deployerAddr = '0xf02e236F9F6C08966DD63B9fB9C04764E01b0563'; // address of deployed Deployer contract 19 | const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 20 | 21 | 22 | describe('[eth/counterfactual] buildCreate2Address', () => { 23 | it('create counterfactual address from contract', () => { 24 | // read bytecode from the contract file 25 | const bytecode = iden3.counterfactual.readContractFile('./src/eth/testbytecode.json'); 26 | const salt = 0; 27 | 28 | const kop = addressHex; 29 | const krec = addressHex; 30 | const krev = addressHex; 31 | expect(addressHex).to.be.equal('0xbc8c480e68d0895f1e410f4e4ea6e2d6b160ca9f'); 32 | 33 | const bytecodefull = `${bytecode}${iden3.counterfactual.encodeParam('address', kop).slice(2)}${ 34 | iden3.counterfactual.encodeParam('address', relayAddr).slice(2)}${ 35 | iden3.counterfactual.encodeParam('address', krec).slice(2)}${ 36 | iden3.counterfactual.encodeParam('address', krev).slice(2)}${ 37 | iden3.counterfactual.encodeParam('address', iden3implAddr).slice(2)}`; 38 | 39 | // middle step check bytecodefull equal to go-iden3 implementation 40 | expect(web3.utils.sha3(bytecodefull)).to.be.equal('0xcd37d9e0add749ea7f03b9f99597ac17c6264f1c4800991fb15602c7802e0969'); 41 | 42 | const computedAddr = iden3.counterfactual.buildCreate2Address(iden3deployerAddr, salt, bytecodefull); 43 | expect(computedAddr).to.be.equal('0x52dc5fa952194ad6c3268666fc4e64407a1d457a'); 44 | }); 45 | }); 46 | 47 | describe('[eth/counterfactual] calculateIdAddress', () => { 48 | it('calculate idAddr using the counterfactual contract', () => { 49 | // read bytecode from the contract file 50 | const bytecode = iden3.counterfactual.readContractFile('./src/eth/testbytecode.json'); 51 | 52 | const kop = addressHex; 53 | const krec = addressHex; 54 | const krev = addressHex; 55 | expect(addressHex).to.be.equal('0xbc8c480e68d0895f1e410f4e4ea6e2d6b160ca9f'); 56 | 57 | 58 | const computedAddr = iden3.counterfactual.calculateIdAddress(kop, krec, krev, relayAddr, iden3implAddr, iden3deployerAddr, bytecode); 59 | expect(computedAddr).to.be.equal('0x52dc5fa952194ad6c3268666fc4e64407a1d457a'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/eth/testbytecode.json: -------------------------------------------------------------------------------- 1 | { 2 | "bytecode": "0x608060405234801561001057600080fd5b5060405160a0806108eb8339810180604052810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190505050838282856100778261025e640100000000026401000000009004565b61008f816102a8640100000000026401000000009004565b50506100d8827f669b373ede2d753c867ddc72899bdfdaec8f8b75c38e74875af8e9f1574745f9600102600019166102f2640100000000026105b0179091906401000000009004565b61011f817fdea267dffcb92b0bd25897bac6eb57d8f594c51f6694fa9673b602fa6f8c3446600102600019166102f2640100000000026105b0179091906401000000009004565b50507f55353cd176c454ba526e09653a5a00a4cc4d1468d129208df7f84e0a359f688d8585858585604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019550505050505060405180910390a150505050506102f9565b6102a5817f1233c892137aa32e0f1c4b5359db9038b88dde2eb3927c5ee83dc8c9b3e17c0f600102600019166102f2640100000000026105b0179091906401000000009004565b50565b6102ef817f44f094e9bb0645dc669d08e0f0767e604381cfc211e15a1dbae98fd3b535f285600102600019166102f2640100000000026105b0179091906401000000009004565b50565b8082555050565b6105e3806103086000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632d5424d5146100b2578063855ac7ef146100e3578063c329064914610134575b34801561006357600080fd5b50600061006e610185565b505090506100af816000368080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050610225565b50005b3480156100be57600080fd5b506100e16004803603810190808035600019169060200190929190505050610254565b005b3480156100ef57600080fd5b506101326004803603810190808035600019169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610338565b005b34801561014057600080fd5b506101836004803603810190808035600019169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610413565b005b60008060006101ba7f1233c892137aa32e0f1c4b5359db9038b88dde2eb3927c5ee83dc8c9b3e17c0f600102600019166104ee565b6101ea7f44f094e9bb0645dc669d08e0f0767e604381cfc211e15a1dbae98fd3b535f285600102600019166104ee565b61021a7f9e67005d2760c1e41a2ea81ffa69f265791c0630cb5295c10534812389883dc4600102600019166104ee565b925092509250909192565b6000612710905060008083516020850186855a03f43d604051816000823e8260008114610250578282f35b8282fd5b6000807f75c3c288bdb83e39db5c3585932ba873ce82233d10024c56f7c300f66049cff26001026000191683600019161415156102d957610293610185565b505091506102d4826000368080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050610225565b610333565b6102e1610185565b925050503373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151561031f57600080fd5b610328336104f9565b6103326000610536565b5b505050565b6000807f75c3c288bdb83e39db5c3585932ba873ce82233d10024c56f7c300f66049cff26001026000191684600019161415156103bd57610377610185565b505091506103b8826000368080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050610225565b61040d565b6103c5610185565b509150503373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151561040357600080fd5b61040c83610536565b5b50505050565b6000807f75c3c288bdb83e39db5c3585932ba873ce82233d10024c56f7c300f66049cff260010260001916846000191614151561049857610452610185565b50509150610493826000368080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050610225565b6104e8565b6104a0610185565b509150503373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415156104de57600080fd5b6104e783610573565b5b50505050565b600081549050919050565b610533817f44f094e9bb0645dc669d08e0f0767e604381cfc211e15a1dbae98fd3b535f285600102600019166105b090919063ffffffff16565b50565b610570817f9e67005d2760c1e41a2ea81ffa69f265791c0630cb5295c10534812389883dc4600102600019166105b090919063ffffffff16565b50565b6105ad817f1233c892137aa32e0f1c4b5359db9038b88dde2eb3927c5ee83dc8c9b3e17c0f600102600019166105b090919063ffffffff16565b50565b80825550505600a165627a7a72305820387245fd295e4d233b04ff47b0b758d40bfdcef244f6bd87c173d75fdba80f280029" 3 | } 4 | -------------------------------------------------------------------------------- /src/id/id-utils.js: -------------------------------------------------------------------------------- 1 | import { AuthorizeKSignBabyJub, AuthorizeEthKey } from '../claim/claim'; 2 | 3 | const bs58 = require('bs58'); 4 | const utils = require('../utils'); 5 | const Db = require('../db/db'); 6 | const smt = require('../sparse-merkle-tree/sparse-merkle-tree'); 7 | 8 | const TypeBJM7 = Buffer.from([0x00, 0x00]); 9 | // const TypeS2M7 = Buffer.from([0x00, 0x04]); 10 | 11 | /** 12 | * from a given id (Buffer), returns an object containing: 13 | * { 14 | * type, 15 | * genesis, 16 | * checksum 17 | * } 18 | * @param {Buffer} id - id 19 | * @returns {Object} - object {type, genesis, checksum} 20 | */ 21 | function decomposeID(id) { 22 | const typ = id.slice(0, 2); 23 | const genesis = id.slice(2, id.length - 2); 24 | const checksum = id.slice(id.length - 2, id.length); 25 | return { 26 | type: typ, 27 | genesis, 28 | checksum, 29 | }; 30 | } 31 | 32 | /** 33 | * Calculates the checksum for a given type & genesis 34 | * @param {Buffer} typ - type of identity specification 35 | * @param {Buffer} genesis - genesis root of the id state: id_0.0 36 | * @returns {Buffer} checksum 37 | */ 38 | function calculateChecksum(typ, genesis) { 39 | const toHash = Buffer.concat([typ, genesis]); 40 | const h = utils.hashBytes(toHash); 41 | const checksum = h.slice(h.length - 2, h.length); 42 | return checksum; 43 | } 44 | 45 | /** 46 | * checks the checksum of a given identity 47 | * @param {Buffer} id - id 48 | * @returns {bool} - true if the checksum is correct, false if not 49 | */ 50 | function checkChecksum(id) { 51 | const decomposed = decomposeID(id); 52 | const c = calculateChecksum(decomposed.typ, decomposed.genesis); 53 | return Buffer.compare(decomposed.checksum, c); 54 | } 55 | 56 | /** 57 | * parse identity from buffer, checking the checksum 58 | * @param {string} s - id in base58 string representation 59 | * @returns {Buffer} id 60 | */ 61 | function idFromBuffer(b) { 62 | if (b.length !== 31) { 63 | throw new Error('id error: not valid length'); 64 | } 65 | if (!checkChecksum(b)) { 66 | throw new Error('id error: checksum verification error'); 67 | } 68 | return b; 69 | } 70 | 71 | /** 72 | * parse identity from string, checking the checksum 73 | * @param {string} s - id in base58 string representation 74 | * @returns {Buffer} id 75 | */ 76 | function idFromString(s) { 77 | const b = bs58.decode(s); 78 | return idFromBuffer(b); 79 | } 80 | 81 | /** 82 | * creates an identity given type & genesis 83 | * where the id will be [ typ | genesis | checksum ] 84 | * @param {Buffer} typ - type of identity specification 85 | * @param {Buffer} genesis - genesis root of the id state: id_0.0 86 | * @returns {Buffer} id 87 | */ 88 | function newID(typ, genesis) { 89 | const checksum = calculateChecksum(typ, genesis); 90 | // as this is not a typed language, the .slice(0, x) is to make sure that the variables are of the desired length 91 | const id = Buffer.concat([typ.slice(0, 2), genesis.slice(0, 27), checksum.slice(0, 2)]); 92 | return id; 93 | } 94 | 95 | /** 96 | * calculates the Id Genesis, from given public keys 97 | * @param {String} kop - compressed babyjub public key in hex string representation 98 | * @param {String} kdis - eth addr in hex string 99 | * @param {String} kreen - eth addr in hex string 100 | * @returns {String} idGenesis - hex representation of the IdGenesis 101 | */ 102 | function calculateIdGenesis(kop, kdis, kreen) { 103 | const db = new Db.Memory(false); 104 | const mt = new smt.SparseMerkleTree(db, ''); 105 | 106 | const claimKOp = new AuthorizeKSignBabyJub(kop); 107 | mt.addEntry(claimKOp.toEntry()); 108 | 109 | const claimKDis = new AuthorizeEthKey(kdis, 0); 110 | mt.addEntry(claimKDis.toEntry()); 111 | 112 | const claimKReen = new AuthorizeEthKey(kreen, 1); 113 | mt.addEntry(claimKReen.toEntry()); 114 | 115 | const idGenesisBuffer = mt.root.slice(0, 27); 116 | const id = newID(TypeBJM7, idGenesisBuffer); 117 | return bs58.encode(id); 118 | } 119 | 120 | 121 | module.exports = { 122 | newID, 123 | idFromString, 124 | idFromBuffer, 125 | decomposeID, 126 | calculateChecksum, 127 | checkChecksum, 128 | calculateIdGenesis, 129 | }; 130 | -------------------------------------------------------------------------------- /src/id/id-utils.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const iden3 = require('../index'); 3 | 4 | const { expect } = chai; 5 | 6 | describe('[id utils] calculateIdGenesis()', () => { 7 | it('check calculateIdGenesis()', () => { 8 | // const privKey = '0x4be5471a938bdf3606888472878baace4a6a64e14a153adf9a1333969e4e573c'; 9 | // const privKeyBuff = iden3.utils.hexToBytes(privKey); 10 | // const pubKeyBuff = utilsBabyJub.privToPub(privKeyBuff, true); 11 | // console.log("pbstr", iden3.utils.bytesToHex(pubKeyBuff)); 12 | 13 | // public keys 14 | const kopStr = '0x966764905ac3e864c4bad1641659eda209b551b4cd78b08073db328b270a7f11'; // getted from the last 5 lines 15 | const kdisStr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 16 | const kreenStr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 17 | 18 | // check that with same keys that in go-iden3 test, gives the same idAddr than in go-iden3 19 | const idAddr = iden3.idUtils.calculateIdGenesis(kopStr, kdisStr, kreenStr); 20 | expect(idAddr).to.be.equal('113CNy9fi6By6nGDJhw7nNok78S6HcV777SPQy1Sev'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/id/id.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Db = require('../db/db'); 3 | const KeyContainer = require('../key-container/key-container'); 4 | const Id = require('./id'); 5 | 6 | const { expect } = chai; 7 | 8 | describe('[Identity management]', () => { 9 | let dataBase; 10 | let keyContainer; 11 | before('Create local storage container', () => { 12 | dataBase = new Db.LocalStorage(); 13 | keyContainer = new KeyContainer(dataBase); 14 | }); 15 | 16 | it('Generate first identity', () => { 17 | const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye'; 18 | keyContainer.unlock('pass'); 19 | after(() => { keyContainer.lock(); }); 20 | // Generate key master in key container 21 | keyContainer.setMasterSeed(mnemonic); 22 | // Generate keys for first identity 23 | const keys = keyContainer.createKeys(); 24 | const identity = new Id(keys.kOp, keys.kDis, keys.kReen, 'relay test', 0); 25 | // Save keys and retrieve it 26 | identity.saveKeys(); 27 | const keysDb = identity.getKeys(); 28 | expect(keys.kOp.toString()).to.be.equal(keysDb.operationalPub); 29 | expect(keys.kDis).to.be.equal(keysDb.disablePub); 30 | expect(keys.kReen).to.be.equal(keysDb.reenablePub); 31 | // Create new key for the identity 32 | const loginKey = identity.createKey(keyContainer, 'login Key'); 33 | // Retrieve keys 34 | const keysDb2 = identity.getKeys(); 35 | expect(loginKey).to.be.equal(keysDb2['login Key']); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/identity/identity-utils.js: -------------------------------------------------------------------------------- 1 | const bs58 = require('bs58'); 2 | const Db = require('../db/db'); 3 | const smt = require('../sparse-merkle-tree/sparse-merkle-tree'); 4 | const claim = require('../claim/claim'); 5 | const proofs = require('../protocols/proofs.js'); 6 | 7 | // const TypeBJM7 = Buffer.from([0x00, 0x00]); 8 | const TypeBJP0 = Buffer.from([0x00, 0x01]); 9 | // const TypeS2M7 = Buffer.from([0x00, 0x04]); 10 | 11 | /** 12 | * from a given id (Buffer), returns an object containing: 13 | * { 14 | * type, 15 | * genesis, 16 | * checksum 17 | * } 18 | * @param {Buffer} id - id 19 | * @returns {Object} - object {type, genesis, checksum} 20 | */ 21 | function decomposeID(id) { 22 | const typ = id.slice(0, 2); 23 | const genesis = id.slice(2, id.length - 2); 24 | const checksum = id.slice(id.length - 2, id.length); 25 | return { 26 | type: typ, 27 | genesis, 28 | checksum, 29 | }; 30 | } 31 | 32 | /** 33 | * Calculates the checksum for a given type & genesis 34 | * @param {Buffer} typ - type of identity specification 35 | * @param {Buffer} genesis - genesis root of the id state: id_0.0 36 | * @returns {Buffer} checksum 37 | */ 38 | function calculateChecksum(typ, genesis) { 39 | const toChecksum = Buffer.concat([typ, genesis]); 40 | let s = 0; 41 | for (let i = 0; i < toChecksum.length; i++) { 42 | s += toChecksum[i]; 43 | } 44 | const checksum = Buffer.alloc(2); 45 | checksum[0] = s >> 8; 46 | checksum[1] = s & 0xff; 47 | return checksum; 48 | } 49 | 50 | /** 51 | * checks the checksum of a given identity 52 | * @param {Buffer} id - id 53 | * @returns {bool} - true if the checksum is correct, false if not 54 | */ 55 | function checkChecksum(id) { 56 | const decomposed = decomposeID(id); 57 | const c = calculateChecksum(decomposed.type, decomposed.genesis); 58 | return (Buffer.compare(decomposed.checksum, c) === 0); 59 | } 60 | 61 | /** 62 | * parse identity from buffer, checking the checksum 63 | * @param {string} s - id in base58 string representation 64 | * @returns {Buffer} id 65 | */ 66 | function idFromBuffer(b) { 67 | if (b.length !== 31) { 68 | throw new Error('id error: not valid length'); 69 | } 70 | if (!checkChecksum(b)) { 71 | throw new Error('id error: checksum verification error'); 72 | } 73 | return b; 74 | } 75 | 76 | /** 77 | * parse identity from string, checking the checksum 78 | * @param {string} s - id in base58 string representation 79 | * @returns {Buffer} id 80 | */ 81 | function idFromString(s) { 82 | const b = bs58.decode(s); 83 | return idFromBuffer(b); 84 | } 85 | /** 86 | * get identity base58 string from buffer, checking the checksum 87 | * @param {Buffer} b - id in Buffer format 88 | * @returns {String} id in String base58 format 89 | */ 90 | function stringFromBufferId(b) { 91 | if (!checkChecksum(b)) { 92 | throw new Error('id error: checksum verification error'); 93 | } 94 | return bs58.encode(b); 95 | } 96 | 97 | 98 | /** 99 | * creates an identity given type & genesis 100 | * where the id will be [ typ | genesis | checksum ] 101 | * @param {Buffer} typ - type of identity specification 102 | * @param {Buffer} genesis - genesis root of the id state: id_0.0 103 | * @returns {Buffer} id 104 | */ 105 | function newID(typ, genesis) { 106 | const checksum = calculateChecksum(typ, genesis); 107 | // as this is not a typed language, the .slice(0, x) is to make sure that the variables are of the desired length 108 | const id = Buffer.concat([typ.slice(0, 2), genesis.slice(0, 27), checksum.slice(0, 2)]); 109 | return id; 110 | } 111 | 112 | /** 113 | * calculates the Id Genesis, from given public keys 114 | * @param {String} kop - compressed babyjub public key in hex string representation 115 | * @param {String} kdis - eth addr in hex string 116 | * @param {String} kreen - eth addr in hex string 117 | * @param {String} kupdateRoot - eth addr in hex string 118 | * @returns {String} idGenesis - hex representation of the IdGenesis 119 | */ 120 | function calculateIdGenesis(kop, kdis, kreen, kupdateRoot) { 121 | const db = new Db.Memory(false); 122 | const mt = new smt.SparseMerkleTree(db, ''); 123 | 124 | const claimKOp = new claim.AuthorizeKSignBabyJub(kop); 125 | mt.addEntry(claimKOp.toEntry()); 126 | 127 | const claimKDis = new claim.AuthorizeEthKey(kdis, claim.ETH_KEY_TYPE.DISABLE); 128 | mt.addEntry(claimKDis.toEntry()); 129 | 130 | const claimKReen = new claim.AuthorizeEthKey(kreen, claim.ETH_KEY_TYPE.REENABLE); 131 | mt.addEntry(claimKReen.toEntry()); 132 | 133 | const claimKUpdateRoot = new claim.AuthorizeEthKey(kupdateRoot, claim.ETH_KEY_TYPE.UPDATE_ROOT); 134 | mt.addEntry(claimKUpdateRoot.toEntry()); 135 | 136 | const proofClaimKOp = proofs.getProofClaimByHi(mt, claimKOp.toEntry().hiBigInt()); 137 | const proofClaimKDis = proofs.getProofClaimByHi(mt, claimKDis.toEntry().hiBigInt()); 138 | const proofClaimKReen = proofs.getProofClaimByHi(mt, claimKReen.toEntry().hiBigInt()); 139 | const proofClaimKeyUpdateRoot = proofs.getProofClaimByHi(mt, claimKUpdateRoot.toEntry().hiBigInt()); 140 | 141 | const idGenesisBuffer = mt.root.slice(mt.root.length - 27, mt.root.lenth); 142 | const id = newID(TypeBJP0, idGenesisBuffer); 143 | 144 | return { 145 | id: bs58.encode(id), 146 | proofClaimKeyOperationalPub: proofClaimKOp, 147 | proofClaimKeyDisable: proofClaimKDis, 148 | proofClaimKeyReenable: proofClaimKReen, 149 | proofClaimKeyUpdateRoot, 150 | }; 151 | } 152 | 153 | 154 | module.exports = { 155 | newID, 156 | idFromString, 157 | idFromBuffer, 158 | stringFromBufferId, 159 | decomposeID, 160 | calculateChecksum, 161 | checkChecksum, 162 | calculateIdGenesis, 163 | }; 164 | -------------------------------------------------------------------------------- /src/identity/identity-utils.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const iden3 = require('../index'); 3 | const eddsa = require('../crypto/eddsa-babyjub.js'); 4 | 5 | const { expect } = chai; 6 | 7 | describe('[identity-utils] calculateIdGenesis()', () => { 8 | it('check calculateIdGenesis()', () => { 9 | const privKey = '0x28156abe7fe2fd433dc9df969286b96666489bac508612d0e16593e944c4f69f'; 10 | const sk = new eddsa.PrivateKey(iden3.utils.hexToBytes(privKey)); 11 | const pk = sk.public(); 12 | 13 | const kopStr = pk.toString(); 14 | // kopStr: 0xab05184c7195b259c95169348434f3a7228fbcfb187d3b07649f3791330cf05c 15 | // public keys 16 | const kdisStr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 17 | const kreenStr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 18 | const kupdateRoot = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 19 | 20 | // check that with same keys that in go-iden3 test, gives the same idAddr than in go-iden3 21 | const idAddr = iden3.identityUtils.calculateIdGenesis(kopStr, kdisStr, kreenStr, kupdateRoot); 22 | expect(idAddr.id).to.be.equal('1LzwQet8DMLnYKBz2WgUvL3WDfjbbPrkAmcekMSUP'); // same result as in go-iden3-core/core/id_test.go:135 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/identity/identity.js: -------------------------------------------------------------------------------- 1 | const ethUtil = require('ethereumjs-util'); 2 | const bip39 = require('bip39'); 3 | 4 | const KeyContainer = require('../key-container/key-container'); 5 | const identityUtils = require('./identity-utils.js'); 6 | 7 | /** 8 | * Class representing a user identity 9 | * Manage all possible actions related to identity usage 10 | */ 11 | class Identity { 12 | /** 13 | * @param {Object} db - database to use 14 | * @param {Object} keyContainer - keyContainer to use 15 | * @param {String} id - id in base58 string willing to load 16 | * @param {String} keyOperationalPub - string public key 17 | * @param {String} keyDisable - string ethereum key 18 | * @param {String} keyReenable - string ethereum key 19 | * @param {String} keyUpdateRoot - string ethereum key 20 | */ 21 | constructor(db, keyContainer, id, keyOperationalPub, keyDisable, keyReenable, keyUpdateRoot, 22 | proofClaimKeyOperationalPub, proofClaimKeyDisable, proofClaimKeyReenable, proofClaimKeyUpdateRoot) { 23 | this.db = db; 24 | this.keyContainer = keyContainer; 25 | 26 | this.keyOperationalPub = keyOperationalPub; 27 | this.keyDisable = keyDisable; 28 | this.keyReenable = keyReenable; 29 | this.keyUpdateRoot = keyUpdateRoot; 30 | 31 | this.proofClaimKeyOperationalPub = proofClaimKeyOperationalPub; 32 | this.proofClaimKeyDisable = proofClaimKeyDisable; 33 | this.proofClaimKeyReenable = proofClaimKeyReenable; 34 | this.proofClaimKeyUpdateRoot = proofClaimKeyUpdateRoot; 35 | 36 | this.id = id; 37 | } 38 | 39 | /** 40 | * @param {Object} db - database to use 41 | * @param {String} passphrase - passphrase to encrypt/decrypt the keyContainer 42 | * @param {String} seed - (optional) mnemonic seed to generate the keys 43 | */ 44 | static create(db, passphrase, seed) { 45 | const keyContainer = new KeyContainer(db); 46 | keyContainer.unlock(passphrase); 47 | 48 | if (seed === undefined) { 49 | seed = bip39.generateMnemonic(); 50 | } 51 | keyContainer.setMasterSeed(seed); 52 | // seed = keyContainer.getMasterSeed(); 53 | const keys = keyContainer.createKeys(); 54 | 55 | const keyOperationalPub = keys.kOp; 56 | db.insert('keyOperationalPub', keyOperationalPub); 57 | 58 | const keyDisablePub = keys.kDis; 59 | const keyDisable = `0x${ethUtil.pubToAddress(keyDisablePub, true).toString('hex')}`; 60 | db.insert('keyDisable', keyDisable); 61 | const keyReenablePub = keys.kReen; 62 | const keyReenable = `0x${ethUtil.pubToAddress(keyReenablePub, true).toString('hex')}`; 63 | db.insert('keyReenable', keyReenable); 64 | const keyUpdateRootPub = keys.kUpdateRoot; 65 | const keyUpdateRoot = `0x${ethUtil.pubToAddress(keyUpdateRootPub, true).toString('hex')}`; 66 | db.insert('keyUpdateRoot', keyUpdateRoot); 67 | 68 | const { 69 | id, proofClaimKeyOperationalPub, proofClaimKeyDisable, proofClaimKeyReenable, proofClaimKeyUpdateRoot, 70 | } = identityUtils.calculateIdGenesis(keyOperationalPub, keyReenable, keyDisable, keyUpdateRoot); 71 | db.insert('id', id); 72 | db.insert(id, true); 73 | 74 | db.insert('proofClaimKeyOperationalPub', JSON.stringify(proofClaimKeyOperationalPub)); 75 | db.insert('proofClaimKeyDisable', JSON.stringify(proofClaimKeyDisable)); 76 | db.insert('proofClaimKeyReenable', JSON.stringify(proofClaimKeyReenable)); 77 | db.insert('proofClaimKeyUpdateRoot', JSON.stringify(proofClaimKeyUpdateRoot)); 78 | 79 | return new Identity( 80 | db, keyContainer, id, 81 | keyOperationalPub, keyDisable, keyReenable, keyUpdateRoot, 82 | proofClaimKeyOperationalPub, proofClaimKeyDisable, proofClaimKeyReenable, proofClaimKeyUpdateRoot, 83 | ); 84 | } 85 | 86 | /** 87 | * @param {Object} db - database to use 88 | * @param {String} id - id in base58 string willing to load 89 | */ 90 | static load(db, id) { 91 | if (db.get(id) === null) { 92 | throw new Error(`id ${id} not found in db`); 93 | } 94 | const keyOperationalPub = db.get('keyOperationalPub'); 95 | const keyDisable = db.get('keyDisable'); 96 | const keyReenable = db.get('keyReenable'); 97 | const keyUpdateRoot = db.get('keyUpdateRoot'); 98 | 99 | const proofClaimKeyOperationalPub = JSON.parse(db.get('proofClaimKeyOperationalPub')); 100 | const proofClaimKeyDisable = JSON.parse(db.get('proofClaimKeyDisable')); 101 | const proofClaimKeyReenable = JSON.parse(db.get('proofClaimKeyReenable')); 102 | const proofClaimKeyUpdateRoot = JSON.parse(db.get('proofClaimKeyUpdateRoot')); 103 | const keyContainer = new KeyContainer(db); 104 | return new Identity( 105 | db, keyContainer, id, 106 | keyOperationalPub, keyDisable, keyReenable, keyUpdateRoot, 107 | proofClaimKeyOperationalPub, proofClaimKeyDisable, proofClaimKeyReenable, proofClaimKeyUpdateRoot, 108 | ); 109 | } 110 | } 111 | 112 | module.exports = Identity; 113 | -------------------------------------------------------------------------------- /src/identity/identity.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Db = require('../db/db'); 3 | const iden3 = require('../index.js'); 4 | 5 | const { expect } = chai; 6 | 7 | const passphrase = 'pass'; 8 | 9 | describe('[identity]', () => { 10 | let db0; 11 | let db1; 12 | before('create dbs', () => { 13 | db0 = new Db.LocalStorage('db0'); 14 | db1 = new Db.LocalStorage('db1'); 15 | }); 16 | it('Generate random identity', () => { 17 | const identity = iden3.Identity.create(db0, passphrase); 18 | const idBuff = iden3.identityUtils.idFromString(identity.id); 19 | expect(iden3.identityUtils.checkChecksum(idBuff)).to.be.equal(true); 20 | expect(iden3.identityUtils.stringFromBufferId(idBuff)).to.be.equal(identity.id); 21 | identity.keyContainer.lock(); 22 | }); 23 | it('Generate identity', () => { 24 | const seed = 'walk gravity scout labor eight usual blame warm unlock crane private rival'; 25 | const identity = iden3.Identity.create(db1, passphrase, seed); 26 | expect(identity.id).to.be.equal('1Nvxd2Mm7vpdpHxWEobmoW3mC7yN63ufRnSLBwXTM'); 27 | expect(identity.keyOperationalPub).to.be.equal('76dc2c5998d0f604b21c79c5db79eea984b71412846a100296e22295b70f11aa'); 28 | expect(identity.keyDisable).to.be.equal('0x6a5bf2b3fe6fff9ec56eba2873a42dea037b5595'); 29 | expect(identity.keyReenable).to.be.equal('0xf2996bf50d4bda42c966d51118bd01cab655a0a1'); 30 | 31 | expect(iden3.identityUtils.checkChecksum(iden3.identityUtils.idFromString('117h4RGBgK2yUZDVXiW3Q3y8vTCfttbYAtK7jbsz5a'))).to.be.equal(true); 32 | const idBuff = iden3.identityUtils.idFromString(identity.id); 33 | expect(iden3.identityUtils.checkChecksum(idBuff)).to.be.equal(true); 34 | identity.keyContainer.lock(); 35 | }); 36 | it('Load identity', () => { 37 | const identity = iden3.Identity.load(db1, '1Nvxd2Mm7vpdpHxWEobmoW3mC7yN63ufRnSLBwXTM'); 38 | 39 | expect(identity.id).to.be.equal('1Nvxd2Mm7vpdpHxWEobmoW3mC7yN63ufRnSLBwXTM'); 40 | expect(identity.keyOperationalPub).to.be.equal('76dc2c5998d0f604b21c79c5db79eea984b71412846a100296e22295b70f11aa'); 41 | expect(identity.keyDisable).to.be.equal('0x6a5bf2b3fe6fff9ec56eba2873a42dea037b5595'); 42 | expect(identity.keyReenable).to.be.equal('0xf2996bf50d4bda42c966d51118bd01cab655a0a1'); 43 | 44 | identity.keyContainer.lock(); 45 | }); 46 | it('Load non existing identity in db', () => { 47 | // as we are trying to get an id that is not in the db0, should return an error 48 | expect(() => iden3.identity.load(db0, '11eEdfGdcw6CuSEaF5StaZPP6iEA9DoqXdbhh6wAo')).to.throw(Error); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const sparseMerkleTree = require('./sparse-merkle-tree/sparse-merkle-tree'); 2 | const smtUtils = require('./sparse-merkle-tree/sparse-merkle-tree-utils'); 3 | const Relay = require('./api-client/relay'); 4 | const NameServer = require('./api-client/name-server'); 5 | const Db = require('./db/db'); 6 | const Backup = require('./api-client/private-folder'); 7 | const notifications = require('./api-client/notification-server'); 8 | const nameResolver = require('./api-client/name-resolver'); 9 | const discovery = require('./api-client/discovery'); 10 | const KeyContainer = require('./key-container/key-container'); 11 | const Id = require('./id/id'); 12 | const Identity = require('./identity/identity'); 13 | const identityUtils = require('./identity/identity-utils'); 14 | const idUtils = require('./id/id-utils'); 15 | // const dapp = require('./auth/dapp'); 16 | const utils = require('./utils'); 17 | const auth = require('./auth/auth'); 18 | const admin = require('./admin/requests'); 19 | const protocols = require('./protocols/protocols'); 20 | const counterfactual = require('./eth/counterfactual'); 21 | const constants = require('./constants'); 22 | const claim = require('./claim/claim'); 23 | const crypto = require('./crypto/crypto'); 24 | 25 | const { Auth } = auth; 26 | // const { Dapp } = dapp; 27 | 28 | module.exports = { 29 | notifications, 30 | constants, 31 | sparseMerkleTree, 32 | smtUtils, 33 | Backup, 34 | KeyContainer, 35 | Id, 36 | idUtils, 37 | Identity, 38 | identityUtils, 39 | Relay, 40 | NameServer, 41 | auth, 42 | Auth, 43 | // dapp, 44 | // Dapp, 45 | utils, 46 | admin, 47 | protocols, 48 | counterfactual, 49 | claim, 50 | nameResolver, 51 | discovery, 52 | Db, 53 | crypto, 54 | }; 55 | -------------------------------------------------------------------------------- /src/key-container/kc-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const ethUtil = require('ethereumjs-util'); 4 | const pbkdf2 = require('pbkdf2-sha256'); 5 | const nacl = require('tweetnacl'); 6 | const sealedBox = require('tweetnacl-sealedbox-js'); 7 | nacl.util = require('tweetnacl-util'); 8 | const utils = require('../utils'); 9 | 10 | const errorLockedMsg = 'Key Container is locked'; 11 | const errorKeySeedNoExistMsg = 'key seed doesn\'t exists'; 12 | const errorFailDecryptMsg = 'Could not decrypt message'; 13 | const errorDbKeyNoExistMsg = 'DB Key doesn\'t exist'; 14 | 15 | /** 16 | * @param {Buffer} msg 17 | * @param {Buffer} msgHash 18 | * @param {Buffer} v 19 | * @param {Buffer} r 20 | * @param {Buffer} s 21 | * @returns {Object} 22 | */ 23 | function concatSignature(msg: Buffer, msgHash: Buffer, v: Buffer, r: Buffer, s: Buffer): Object { 24 | let serialized = Buffer.from(''); 25 | 26 | serialized = Buffer.concat([serialized, r]); 27 | serialized = Buffer.concat([serialized, s]); 28 | serialized = Buffer.concat([ 29 | serialized, 30 | Buffer.from(v), 31 | ]); 32 | 33 | return { 34 | message: ethUtil.bufferToHex(msg), 35 | messageHash: ethUtil.bufferToHex(msgHash), 36 | v: ethUtil.bufferToHex(v), 37 | r: ethUtil.bufferToHex(r), 38 | s: ethUtil.bufferToHex(s), 39 | signature: ethUtil.bufferToHex(serialized), 40 | }; 41 | } 42 | 43 | /** 44 | * @param {String} key 45 | * @param {String} salt 46 | * @returns {String} - Key encoded in base64 47 | */ 48 | function passToKey(key: string, salt: string): string { 49 | const res = pbkdf2(key, salt, 256, 32); 50 | return nacl.util.encodeBase64(res); 51 | } 52 | 53 | /** 54 | * @param {String} key - Key encoded in base64 55 | * @param {String} msg 56 | * @returns {String} - Encrypted msg in base64 encoding 57 | */ 58 | function encrypt(key: string, msg: string): string { 59 | const newNonce = () => nacl.randomBytes(nacl.secretbox.nonceLength); 60 | const keyUint8Array = nacl.util.decodeBase64(key); 61 | const nonce = newNonce(); 62 | const messageUint8 = nacl.util.decodeUTF8(msg); 63 | const box = nacl.secretbox(messageUint8, nonce, keyUint8Array); 64 | const fullMessage = new Uint8Array(nonce.length + box.length); 65 | 66 | fullMessage.set(nonce); 67 | fullMessage.set(box, nonce.length); 68 | 69 | // base64 full message; 70 | return nacl.util.encodeBase64(fullMessage); 71 | } 72 | 73 | /** 74 | * @param {String} key - Key encoded in base64 75 | * @param {String} messageWithNonce 76 | */ 77 | function decrypt(key: string, messageWithNonce: string): string { 78 | const keyUint8Array = nacl.util.decodeBase64(key); 79 | const messageWithNonceAsUint8Array = nacl.util.decodeBase64(messageWithNonce); 80 | const nonce = messageWithNonceAsUint8Array.slice(0, nacl.secretbox.nonceLength); 81 | const message = messageWithNonceAsUint8Array.slice(nacl.secretbox.nonceLength, messageWithNonce.length); 82 | const decrypted = nacl.secretbox.open(message, nonce, keyUint8Array); 83 | 84 | if (!decrypted) { 85 | throw new Error(errorFailDecryptMsg); 86 | } 87 | // base64 decrypted message 88 | return nacl.util.encodeUTF8(decrypted); 89 | } 90 | 91 | /** 92 | * Function to encrypt data using public key 93 | * @param {String} pubKey - Public key in base64 string representation 94 | * @param {String} data - Data to be encrypted 95 | */ 96 | function encryptBox(pubKey: string, data: string): string { 97 | const pubKeyBuff = utils.base64ToBytes(pubKey); 98 | const dataBuff = nacl.util.decodeUTF8(data); 99 | // Encrypt data 100 | const dataEncrypted = sealedBox.seal(dataBuff, pubKeyBuff); 101 | 102 | return utils.bytesToBase64(Buffer.from(dataEncrypted)); 103 | } 104 | 105 | /** 106 | * Function to decrypt data using key pair: public key and private key 107 | * @param {String} privKey - Private key in base64 string representation 108 | * @param {String} pubKey - Public key in base64 string representation 109 | * @param {String} dataEncrypted - Data to be decrypted in base64 string representation 110 | */ 111 | function decryptBox(privKey: string, pubKey: string, dataEncrypted: string) { 112 | const pubKeyBuff = utils.base64ToBytes(pubKey); 113 | const privKeyBuff = utils.base64ToBytes(privKey); 114 | const dataEncryptedBuff = utils.base64ToBytes(dataEncrypted); 115 | // Decrypt data 116 | const data = sealedBox.open(dataEncryptedBuff, pubKeyBuff, privKeyBuff); 117 | 118 | return nacl.util.encodeUTF8(data); 119 | } 120 | 121 | 122 | module.exports = { 123 | concatSignature, 124 | passToKey, 125 | encrypt, 126 | decrypt, 127 | encryptBox, 128 | decryptBox, 129 | errorLockedMsg, 130 | errorKeySeedNoExistMsg, 131 | errorFailDecryptMsg, 132 | errorDbKeyNoExistMsg, 133 | }; 134 | -------------------------------------------------------------------------------- /src/manager/manager-notification.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const sign = require('../protocols/login'); 4 | 5 | export type Notification = { 6 | id: number, 7 | date: number, 8 | jws: string, 9 | toAddr: string, 10 | }; 11 | 12 | export type NotificationFull = { 13 | id: number, 14 | dateServer: number, 15 | issuer: string, 16 | dateIssuer: number, 17 | type: string, 18 | data: Object, 19 | } 20 | 21 | const TYPE_NOTIFICATIONS = { 22 | PROOFCLAIM: 'notif.claim.v01', 23 | TEXT: 'notif.txt.v01', 24 | }; 25 | 26 | /** 27 | * Class to manage notifications 28 | * Manage all possible actions related to notifications 29 | */ 30 | export class ManagerNotifications { 31 | lastIdNotification: number; 32 | verifier: sign.SignedPacketVerifier; 33 | 34 | /** 35 | * Notification are downloaded and they must be verified first in order to process them afterwards 36 | * @param {SignedPacketVerifier} verifyService - Represents all the functions needed to verify packets 37 | */ 38 | constructor(verifyService: sign.SignedPacketVerifier) { 39 | this.lastIdNotification = 0; 40 | this.verifier = verifyService; 41 | } 42 | 43 | manage(not: NotificationFull): boolean { 44 | const typeNot = not.type; 45 | switch (typeNot) { 46 | case TYPE_NOTIFICATIONS.PROOFCLAIM: 47 | return true; 48 | case TYPE_NOTIFICATIONS.TEXT: 49 | return true; 50 | default: return true; 51 | } 52 | } 53 | 54 | /** 55 | * Verify signed packet and retrieve useful notification information 56 | * @param {Notification} not - Notification object 57 | */ 58 | checkNot(not: Notification): ?NotificationFull { 59 | const result = this.verifier.verifySignedPacketMessage(not.jws); 60 | if (result == null) { 61 | throw new Error('Notification verification failed'); 62 | } 63 | const { header, payload } = result; 64 | const notFull = { 65 | id: not.id, 66 | dateServer: not.date, 67 | issuer: header.iss, 68 | dateIssuer: header.iat, 69 | type: payload.form.type, 70 | data: payload.form.data, 71 | }; 72 | this.manage(notFull); 73 | return notFull; 74 | } 75 | 76 | /** 77 | * Update the last notification identifier 78 | * Used to only retrieve notifications after the last one 79 | * @param {Number} notId - Notification identifier 80 | */ 81 | updateLastId(notId: number) { 82 | if (notId > this.lastIdNotification) { 83 | this.lastIdNotification = notId; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/protocols/README.md: -------------------------------------------------------------------------------- 1 | # iden3js - protocols 2 | 3 | ## Login (Identity Assertion) 4 | 5 | ``` 6 | Wallet Service 7 | + + 8 | | signatureRequest | 9 | | <-------------------------------------+ | 10 | | | 11 | | +---+ | 12 | | | | 13 | | |sign packet | 14 | | | | 15 | | <---+ | 16 | | signedPacket | 17 | | +-------------------------------------> | 18 | | | 19 | | +---+ | 20 | | verify | | 21 | | signedPacket| | 22 | | | | 23 | | +---> | 24 | | | 25 | | ok | 26 | | <-------------------------------------+ | 27 | | | 28 | | | 29 | | | 30 | + + 31 | ``` 32 | 33 | Read the login protocol specification [here](login_spec.md). 34 | 35 | ### Define new NonceDB 36 | ```js 37 | const nonceDB = new iden3.protocols.NonceDB(); 38 | ``` 39 | 40 | ### Generate New Request of Identity Assert 41 | - input 42 | - `nonceDB`: NonceDB class object 43 | - `origin`: domain of the emitter of the request 44 | - `timeout`: unixtime format, valid until that date. We can use for example 2 minutes (`2*60` seconds) 45 | - output 46 | - `signatureRequest`: `Object` 47 | ```js 48 | const signatureRequest = iden3.protocols.login.newRequestIdenAssert(nonceDB, origin, 2*60); 49 | ``` 50 | 51 | The `nonce` of the `signatureRequest` can be getted from: 52 | ```js 53 | const nonce = signatureRequest.body.data.challenge; 54 | // nonce is the string containing the nonce value 55 | ``` 56 | 57 | We can add auxiliar data to the `nonce` in the `nonceDB` only one time: 58 | ```js 59 | const added = nodeDB.addAuxToNonce(nonce, auxdata); 60 | // added is a bool confirming if the aux data had been added 61 | ``` 62 | 63 | ### Sign Packet 64 | - input 65 | - `signatureRequest`: object generated in the `newRequestIdenAssert` function 66 | - `userAddr`: Eth Address of the user that signs the data packet 67 | - `ethName`: name assigned to the `userAddr` 68 | - `proofOfEthName`: `proofOfClaim` of the `ethName` 69 | - `kc`: iden3.KeyContainer object 70 | - `ksign`: KOperational authorized for the `userAddr` 71 | - `proofOfKSign`: `proofOfClaim` of the `ksign` 72 | - `expirationTime`: unixtime format, signature will be valid until that date 73 | - output 74 | - `signedPacket`: `String` 75 | ```js 76 | const expirationTime = unixtime + (3600 * 60); 77 | const signedPacket = iden3.protocols.login.signIdenAssertV01(signatureRequest, usrAddr, ethName, proofOfEthName, kc, ksign, proofOfKSign, expirationTime); 78 | ``` 79 | 80 | ### Verify Signed Packet 81 | - input 82 | - `nonceDB`: NonceDB class object 83 | - `origin`: domain of the emitter of the request 84 | - `signedPacket`: object generated in the `signIdenAssertV01` function 85 | - output 86 | - `nonce`: nonce object of the signedPacket, that has been just deleted from the nonceDB when the signedPacket is verified. If the verification fails, the nonce will be `undefined` 87 | ```js 88 | const verified = iden3.protocols.login.verifySignedPacket(nonceDB, origin, signedPacket); 89 | ``` 90 | 91 | ### Apendix 92 | 93 | See the [login specification document](login_spec.md) for information about the protocol design. 94 | -------------------------------------------------------------------------------- /src/protocols/login_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iden3/iden3js/1d24bbe6923744df4844bff400c8504584cd3c48/src/protocols/login_flow.png -------------------------------------------------------------------------------- /src/protocols/login_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iden3/iden3js/1d24bbe6923744df4844bff400c8504584cd3c48/src/protocols/login_overview.png -------------------------------------------------------------------------------- /src/protocols/login_spec_rationale.md: -------------------------------------------------------------------------------- 1 | # Rationale 2 | 3 | The following document contains references to similar protocols on which our 4 | login protocol relies on or takes inspiration from. 5 | 6 | ## Signature format 7 | 8 | Use JSON to encode the object that will be signed. 9 | 10 | ### JSON Signing formats 11 | 12 | https://medium.facilelogin.com/json-message-signing-alternatives-897f90d411c 13 | 14 | - JSON Web Signature (JWS) 15 | - Doesn't need canonicalization 16 | - Allows signing arbitrary data (not only JSON) 17 | - Widely used 18 | - JSON Cleartext Signature (JCS) 19 | - Concise Binary Object Representation (CBOR) Object Signing 20 | 21 | https://matrix.org/docs/spec/appendices.html#signing-json 22 | 23 | - Matrix JSON Signing 24 | - Allows having multiple signatures with different protocols for a single JSON 25 | 26 | ## Possible attacks 27 | 28 | See WebAuth API, FIDO Threat analysis 29 | 30 | ## References 31 | 32 | - https://en.wikipedia.org/wiki/OpenID 33 | - https://en.wikipedia.org/wiki/OpenID_Connect 34 | - https://en.wikipedia.org/wiki/IndieAuth 35 | - https://fidoalliance.org/how-fido-works/ 36 | 37 | ### WebAuth API 38 | 39 | - https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API 40 | - https://w3c.github.io/webauthn/ 41 | - https://www.w3.org/TR/webauthn/ 42 | 43 | Demo: 44 | - https://www.webauthn.org/ 45 | 46 | FIDO Security guarantees and how they are achieved: 47 | - https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-security-ref-v2.0-id-20180227.html#relation-between-measures-and-goals 48 | - 49 | FIDO Threat analysis and mitigations: 50 | - https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-security-ref-v2.0-id-20180227.html#threat-analysis 51 | 52 | Currently (2018-01-08) there's no support for iOS (Safari): 53 | - https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#Browser_compatibility 54 | 55 | Criticism: 56 | - https://www.scip.ch/en/?labs.20180424 57 | 58 | Example code of server verification: 59 | - https://github.com/duo-labs/webauthn/blob/fa6cd954884baf24fc5a51656ce21c1a1ef574bc/main.go#L336 60 | - https://w3c.github.io/webauthn/#verifying-assertion 61 | 62 | ## Apendix 63 | 64 | ### The FIDO protocols security goals: 65 | 66 | #### [SG-1] 67 | Strong User Authentication: Authenticate (i.e. recognize) a user and/or a device to a relying party with high (cryptographic) strength. 68 | #### [SG-2] 69 | Credential Guessing Resilience: Provide robust protection against eavesdroppers, e.g. be resilient to physical observation, resilient to targeted impersonation, resilient to throttled and unthrottled guessing. 70 | #### [SG-3] 71 | Credential Disclosure Resilience: Be resilient to phishing attacks and real-time phishing attack, including resilience to online attacks by adversaries able to actively manipulate network traffic. 72 | #### [SG-4] 73 | Unlinkablity: Protect the protocol conversation such that any two relying parties cannot link the conversation to one user (i.e. be unlinkable). 74 | #### [SG-5] 75 | Verifier Leak Resilience: Be resilient to leaks from other relying parties. I.e., nothing that a verifier could possibly leak can help an attacker impersonate the user to another relying party. 76 | #### [SG-6] 77 | Authenticator Leak Resilience: Be resilient to leaks from other FIDO Authenticators. I.e., nothing that a particular FIDO Authenticator could possibly leak can help an attacker to impersonate any other user to any relying party. 78 | #### [SG-7] 79 | User Consent: Notify the user before a relationship to a new relying party is being established (requiring explicit consent). 80 | #### [SG-8] 81 | Limited PII: Limit the amount of personal identifiable information (PII) exposed to the relying party to the absolute minimum. 82 | #### [SG-9] 83 | Attestable Properties: Relying Party must be able to verify FIDO Authenticator model/type (in order to calculate the associated risk). 84 | #### [SG-10] 85 | DoS Resistance: Be resilient to Denial of Service Attacks. I.e. prevent attackers from inserting invalid registration information for a legitimate user for the next login phase. Afterward, the legitimate user will not be able to login successfully anymore. 86 | #### [SG-11] 87 | Forgery Resistance: Be resilient to Forgery Attacks (Impersonation Attacks). I.e. prevent attackers from attempting to modify intercepted communications in order to masquerade as the legitimate user and login to the system. 88 | #### [SG-12] 89 | Parallel Session Resistance: Be resilient to Parallel Session Attacks. Without knowing a user’s authentication credential, an attacker can masquerade as the legitimate user by creating a valid authentication message out of some eavesdropped communication between the user and the server. 90 | #### [SG-13] 91 | Forwarding Resistance: Be resilient to Forwarding and Replay Attacks. Having intercepted previous communications, an attacker can impersonate the legal user to authenticate to the system. The attacker can replay or forward the intercepted messages. 92 | #### [SG-14] (not covered by U2F) 93 | Transaction Non-Repudiation: Provide strong cryptographic non-repudiation for secure transactions. 94 | #### [SG-15] 95 | Respect for Operating Environment Security Boundaries: Ensure that registrations and private key material as a shared system resource is appropriately protected according to the operating environment privilege boundaries in place on the FIDO user device. 96 | #### [SG-16] 97 | Assessable Level of Security: Ensure that the design and implementation of the Authenticator allows for the testing laboratory / FIDO Alliance to assess the level of security provided by the Authenticator. 98 | -------------------------------------------------------------------------------- /src/protocols/nonceDB.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type NonceObj = { 4 | nonce: string, 5 | timestamp: number, 6 | aux: any, 7 | }; 8 | 9 | export type NonceResult = { 10 | nonceObj: NonceObj, 11 | index: number, 12 | }; 13 | 14 | /** 15 | * Class representing the NonceDB 16 | */ 17 | export class NonceDB { 18 | nonces: Array; 19 | 20 | /** 21 | * Initialize NonceDB 22 | */ 23 | constructor() { 24 | this.nonces = []; 25 | } 26 | 27 | /** 28 | * Add nonce with a specified timeout 29 | * The nonce will be valid until that timeout 30 | * @param {String} nonce 31 | * @param {Number} timeout - in unixtime format 32 | * @param {Object} auxData - Extra data to be added to NonceObject 33 | */ 34 | _add(nonce: string, timeout: number, auxData: any = undefined): NonceObj { 35 | const nonceObj = { 36 | nonce, 37 | timestamp: timeout, 38 | aux: auxData, 39 | }; 40 | this.nonces.push(nonceObj); 41 | return nonceObj; 42 | } 43 | 44 | /** 45 | * Add nonce with a specified delta 46 | * The nonce will be valid until current-time + delta 47 | * @param {String} nonce 48 | * @param {Number} delta, in seconds unit 49 | * @param {Object} auxData - Extra data to be added to NonceObject 50 | */ 51 | add(nonce: string, delta: number, auxData: any = undefined): NonceObj { 52 | const date = new Date(); 53 | const timestamp = Math.round((date).getTime() / 1000); 54 | const timeout = timestamp + delta; 55 | const nonceObj = this._add(nonce, timeout, auxData); 56 | return nonceObj; 57 | } 58 | 59 | /** 60 | * Add aux to the nonce 61 | * @param {String} nonce 62 | * @param {Object} aux 63 | * @param {bool} 64 | */ 65 | addAuxToNonce(nonce: string, aux: any): boolean { 66 | for (let i = 0; i < this.nonces.length; i++) { 67 | if (this.nonces[i].nonce === nonce) { 68 | if (this.nonces[i].aux !== undefined) { 69 | // can not add an aux if already contains an aux 70 | return false; 71 | } 72 | this.nonces[i].aux = aux; 73 | return true; 74 | } 75 | } 76 | return false; 77 | } 78 | 79 | /** 80 | * Search nonce in NoceDB 81 | * @param {String} nonce 82 | * @returns {Object} 83 | */ 84 | search(nonce: string): ?NonceResult { 85 | this.deleteOld(); 86 | for (let i = 0; i < this.nonces.length; i++) { 87 | if (this.nonces[i].nonce === nonce) { 88 | return { 89 | nonceObj: this.nonces[i], 90 | index: i, 91 | }; 92 | } 93 | } 94 | return undefined; 95 | } 96 | 97 | /** 98 | * Search nonce in NoceDB, and then delete it 99 | * @param {String} nonce 100 | * @returns {bool} 101 | */ 102 | searchAndDelete(nonce: string): ?NonceResult { 103 | const n = this.search(nonce); 104 | if (n == null) { 105 | return undefined; 106 | } 107 | this.nonces.splice(n.index, 1); 108 | return n; 109 | } 110 | 111 | /** 112 | * Delete element in NonceDB 113 | * @param {String} nonce 114 | */ 115 | deleteElem(nonce: string) { 116 | const n = this.search(nonce); 117 | if (n == null) { 118 | return; 119 | } 120 | this.nonces.splice(n.index, 1); 121 | } 122 | 123 | /** 124 | * Delete nonces with timestamp older than the given 125 | * @param {Number} timestamp - if not specified will get current time 126 | */ 127 | deleteOld(timestamp: ?number) { 128 | if (timestamp == null) { 129 | const date = new Date(); 130 | timestamp = Math.round((date).getTime() / 1000); 131 | } 132 | for (let i = 0; i < this.nonces.length; i++) { 133 | if (this.nonces[i].timestamp >= timestamp) { 134 | this.nonces.splice(0, i); 135 | break; 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/protocols/nonceDB.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it } from 'mocha'; 3 | import { NonceDB } from './nonceDB'; 4 | 5 | const chai = require('chai'); 6 | 7 | const { expect } = chai; 8 | 9 | // const crypto = require('crypto'); 10 | 11 | describe('[protocol] nonce', () => { 12 | it('nonce', () => { 13 | const nonceDB = new NonceDB(); 14 | // const date = new Date(); 15 | // let timeout = Math.round((date).getTime() / 1000); 16 | 17 | for (let i = 0; i < 10; i++) { 18 | // const randnonce = crypto.randomBytes(32).toString('base64'); 19 | const randnonce = `asdf${i}`; 20 | nonceDB.add(randnonce, 5); 21 | } 22 | 23 | expect(nonceDB.search('asdf3')).to.be.not.equal(undefined); 24 | expect(nonceDB.search('asdf30')).to.be.equal(undefined); 25 | expect(nonceDB.nonces.length).to.be.equal(10); 26 | nonceDB.deleteElem('asdf4'); 27 | expect(nonceDB.nonces.length).to.be.equal(9); 28 | }); 29 | it('nonce timestamps', () => { 30 | const nonceDB = new NonceDB(); 31 | const date = new Date(); 32 | let timeout = Math.round((date).getTime() / 1000); 33 | 34 | for (let i = 0; i < 10; i++) { 35 | // const randnonce = crypto.randomBytes(32).toString('base64'); 36 | const randnonce = `asdf${i}`; 37 | timeout += 1; 38 | nonceDB._add(randnonce, timeout); 39 | } 40 | 41 | expect(nonceDB.search('asdf3')).to.be.not.equal(undefined); 42 | expect(nonceDB.search('asdf30')).to.be.equal(undefined); 43 | expect(nonceDB.nonces.length).to.be.equal(10); 44 | nonceDB.deleteElem('asdf4'); 45 | expect(nonceDB.nonces.length).to.be.equal(9); 46 | nonceDB.deleteOld(timeout - 5); 47 | expect(nonceDB.nonces.length).to.be.equal(5); 48 | expect(nonceDB.search('asdf3')).to.be.equal(undefined); 49 | }); 50 | it('nonce aux', () => { 51 | const nonceDB = new NonceDB(); 52 | 53 | nonceDB.add('asdf0', 1000); 54 | nonceDB.add('asdf1', 1000); 55 | expect(nonceDB.search('asdf0')).to.be.not.equal(undefined); 56 | expect(nonceDB.addAuxToNonce('asdf0', { 57 | param1: 1, 58 | param2: 2, 59 | })).to.be.equal(true); 60 | // check that one aux can be added only one time in a nonce in the nonceDB 61 | expect(nonceDB.addAuxToNonce('asdf0', { 62 | param1: 1, 63 | param2: 2, 64 | })).to.be.equal(false); 65 | expect(nonceDB.addAuxToNonce('asdf0', 'auxdata')).to.be.equal(false); 66 | expect(nonceDB.addAuxToNonce('asdf1', { 67 | param1: 1, 68 | param2: 2, 69 | })).to.be.equal(true); 70 | const result = (nonceDB.search('asdf0'): any); // This is a type casting 71 | expect(result).to.not.be.equal(undefined); 72 | expect(result.nonceObj.aux.param1).to.be.equal(1); 73 | }); 74 | it('nonce searchAndDelete', () => { 75 | const nonceDB = new NonceDB(); 76 | 77 | nonceDB.add('asdf3', 1000); 78 | nonceDB.addAuxToNonce('asdf3', { 79 | param1: 1, 80 | param2: 2, 81 | }); 82 | const result = (nonceDB.searchAndDelete('asdf3'): any); 83 | expect(result).to.not.be.equal(undefined); 84 | expect(result.nonceObj.aux.param1).to.be.equal(1); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /src/protocols/proofs.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { describe, it } from 'mocha'; 3 | import { Entry } from '../claim/entry'; 4 | 5 | const chai = require('chai'); 6 | const snarkjs = require('snarkjs'); 7 | const iden3 = require('../index'); 8 | 9 | const { bigInt } = snarkjs; 10 | const { expect } = chai; 11 | 12 | function entryFromInts(e0, e1, e2, e3) { 13 | return Entry.newFromBigInts(bigInt(e0), bigInt(e1), bigInt(e2), bigInt(e3)); 14 | } 15 | 16 | const db = new iden3.Db.Memory(); 17 | const idAddr = '0xq5soghj264eax651ghq1651485ccaxas98461251d5f1sdf6c51c5d1c6sd1c651'; 18 | 19 | describe('[proofs] ProofClaim', () => { 20 | it('proof', () => { 21 | const mt = new iden3.sparseMerkleTree.SparseMerkleTree(db, idAddr, 140); 22 | 23 | const claim1 = entryFromInts(33, 44, 55, 66); 24 | const claim2 = entryFromInts(1111, 2222, 3333, 4444); 25 | const claim3 = entryFromInts(5555, 6666, 7777, 8888); 26 | 27 | mt.addEntry(claim1); 28 | mt.addEntry(claim2); 29 | mt.addEntry(claim3); 30 | 31 | const proofClaim = iden3.protocols.getProofClaimByHi(mt, claim1.hiBigInt()); 32 | expect(proofClaim).to.be.not.equal(undefined); 33 | 34 | // Verify 35 | const entry = Entry.newFromHex(proofClaim.leaf); 36 | const hiHex = iden3.utils.bytesToHex(entry.hi()); 37 | const hvHex = iden3.utils.bytesToHex(entry.hv()); 38 | const resCheck = iden3.sparseMerkleTree.checkProof(proofClaim.proofs[0].root, proofClaim.proofs[0].mtp0, 39 | hiHex, hvHex); 40 | expect(resCheck).to.be.equal(true); 41 | // non-existence 42 | iden3.claim.claimUtils.incClaimVersion(entry); 43 | const hiHex2 = iden3.utils.bytesToHex(entry.hi()); 44 | const hvHex2 = iden3.utils.bytesToHex(entry.hv()); 45 | const resCheck2 = iden3.sparseMerkleTree.checkProof(proofClaim.proofs[0].root, proofClaim.proofs[0].mtp1, 46 | hiHex2, hvHex2); 47 | // check 48 | expect(resCheck2).to.be.equal(true); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/protocols/protocols.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const nonceDB = require('./nonceDB'); 3 | const login = require('./login'); 4 | const proofs = require('./proofs'); 5 | 6 | module.exports = { 7 | nonceDB, 8 | login, 9 | verifyProofClaimFull: proofs.verifyProofClaim, 10 | getProofClaimByHi: proofs.getProofClaimByHi, 11 | }; 12 | -------------------------------------------------------------------------------- /src/sparse-merkle-tree/sparse-merkle-tree-utils.js: -------------------------------------------------------------------------------- 1 | import { Entry } from '../claim/entry'; 2 | 3 | const { bigInt } = require('snarkjs'); 4 | 5 | const utils = require('../utils'); 6 | 7 | /** 8 | * Sets bit to 1 into a Uint8 9 | * @param {Uint8} byte - Byte to set the bit 10 | * @returns {Uint8} pos - Position of the bit to set 11 | */ 12 | export function setBit(byte, pos) { 13 | return byte | (0x01 << pos); 14 | } 15 | 16 | /** 17 | * Gets a concrete bit of a Uint8 18 | * @param {Uint8} byte - Byte to get the bit 19 | * @returns {Uint8} pos - Position of the bit to get 20 | */ 21 | export function getBit(byte, pos) { 22 | return (byte >> pos) & 0x01; 23 | } 24 | 25 | /** 26 | * Gets binary representation of leaf position 27 | * @param {bigInt} index - Hash index of the leaf 28 | * @returns {Array(Uint8)} - Array of bits determining leaf position 29 | */ 30 | export function getIndexArray(index) { 31 | let k = bigInt(index); 32 | const res = []; 33 | 34 | while (!k.isZero()) { 35 | if (k.isOdd()) { 36 | res.push(1); 37 | } else { 38 | res.push(0); 39 | } 40 | k = k.shr(1); 41 | } 42 | 43 | while (res.length < 256) res.push(false); 44 | 45 | return res; 46 | } 47 | 48 | /** 49 | * Gets proof object given a string hexadecimal encoded 50 | * @param {String} buffHex - hexadecimal string to parse 51 | * @returns {Object} - proof structure 52 | */ 53 | export function parseProof(buffHex) { 54 | const buffBytes = utils.hexToBytes(buffHex); 55 | const flag = buffBytes.readUInt8(0); 56 | const bitDiff = getBit(flag, 1); 57 | let proofStruct; 58 | if (bitDiff) { 59 | proofStruct = { 60 | flagExistence: flag, 61 | siblingsLength: buffBytes.readUInt8(1), 62 | siblingsBitIndex: buffBytes.slice(2, 32), 63 | siblings: buffBytes.slice(32, buffBytes.length - 32 * 2), 64 | metaData: buffBytes.slice(buffBytes.length - 32 * 2, buffBytes.length), 65 | }; 66 | } else { 67 | proofStruct = { 68 | flagExistence: flag, 69 | siblingsLength: buffBytes.readUInt8(1), 70 | siblingsBitIndex: buffBytes.slice(2, 32), 71 | siblings: buffBytes.slice(32, buffBytes.length), 72 | }; 73 | } 74 | return proofStruct; 75 | } 76 | 77 | /** 78 | * Generates proof object 79 | * @param {String} buffHex - hexadecimal string to parse 80 | * @returns {Object} - structure proof 81 | */ 82 | export function genProofStruct(buffHex) { 83 | const buffBytes = utils.hexToBytes(buffHex); 84 | return { 85 | flagExistence: buffBytes.readUInt8(0), 86 | siblingsLength: buffBytes.readUInt8(0), 87 | siblings: buffBytes.slice(1, buffBytes.length - 2), 88 | metadataNode: buffBytes.slice(buffBytes.length - 2, buffBytes.length), 89 | }; 90 | } 91 | 92 | /** 93 | * Import claims from the claimsDump, generated by go-iden3 in the merkletree.DumpClaims() 94 | * @param {Object} mt 95 | * @param {Object} claimsDump - array of hex strings containing the claimsDump 96 | */ 97 | export function importClaimsDump(mt, claimsDump) { 98 | for (let i = 0; i < claimsDump.length; i++) { 99 | mt.addEntry(Entry.newFromHex(claimsDump[i])); 100 | } 101 | } 102 | 103 | /** 104 | * Retrieve merkle-tree proof type. It can be either existence or non-existence 105 | * @param {String} proofHex - Hexadecimal representation of a merkle-tree proof 106 | * @return {Bool} - Flag indicates if proof is existence (true) or non-existence (false) 107 | */ 108 | export function isMerkleTreeProofExistence(proofHex) { 109 | const proofBuff = parseProof(proofHex); 110 | const flagNonExistence = getBit(proofBuff.flagExistence, 0); 111 | return !flagNonExistence; 112 | } 113 | -------------------------------------------------------------------------------- /src/sparse-merkle-tree/sparse-merkle-tree-utils.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const iden3 = require('../index'); 3 | const smtUtils = require('./sparse-merkle-tree-utils'); 4 | 5 | const { expect } = chai; 6 | 7 | // new database 8 | const db = new iden3.Db.Memory(); 9 | 10 | // claimsDump is the dump of claims from the go-iden3, using merkletree.Walk function, in the TestMTWalkDumpClaims() test function in the go-iden3/merkletree/merkletree_test.go 11 | const claimsDump = [ 12 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031342d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 13 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500382d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 14 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500302d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 15 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500332d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 16 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500342d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 17 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500362d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 18 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031322d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 19 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031312d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 20 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031302d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 21 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031352d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 22 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500372d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 23 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500392d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 24 | '0x01007465737474657374746573747465737474657374746573747465737474657300747465737474657374746573747465737474657374746573747465737474650031332d746573747465737474657374746573747465737474657374746573740074657374746573747465737474657374746573000000000000000000000000', 25 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500312d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 26 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500352d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 27 | '0x010074657374746573747465737474657374746573747465737474657374746573007474657374746573747465737474657374746573747465737474657374746500322d74657374746573747465737474657374746573747465737474657374740065737474657374746573747465737474657374000000000000000000000000', 28 | ]; 29 | 30 | describe('[merkle-tree] Import claims', () => { 31 | it('add claims', () => { 32 | const mt = new iden3.sparseMerkleTree.SparseMerkleTree(db, 'testprefix'); 33 | 34 | smtUtils.importClaimsDump(mt, claimsDump); 35 | 36 | // check that the mt.root is equal to the RootKey of a MerkleTree in go-iden3 containing the same claims 37 | expect(iden3.utils.bytesToHex(mt.root)).to.be.equal('0x1caa9d8ad7a8ad3c6e46fa7101ec5239f869533aa41161db4288c665163c4486'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/id-private-folder.test.js: -------------------------------------------------------------------------------- 1 | // Old test. Future work private folder implementation 2 | 3 | // const chai = require('chai'); 4 | 5 | // const { expect } = chai; 6 | // const iden3 = require('../index'); 7 | 8 | // const testPrivKHex = '8eeba14cc730cc3d8a80d355308904f4239d3550c639137f715b41bd3ee817ce'; 9 | 10 | // const db = new iden3.Db(); 11 | // const backup = new iden3.PrivateFolder('http://127.0.0.1:5001'); 12 | // const kc = new iden3.KeyContainer(db); 13 | 14 | // kc.unlock('pass'); 15 | // const key0id = kc.importKey(testPrivKHex); 16 | // const relay = new iden3.Relay('http://127.0.0.1:8000'); 17 | // const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 18 | // const id = new iden3.Id(key0id, key0id, key0id, relay, relayAddr, ''); 19 | // const kSign = kc.importKey('0dbabbab11336c9f0dfdf583309d56732b1f8a15d52a7412102f49cf5f344d05'); 20 | 21 | 22 | // let proofOfKSign = {}; 23 | 24 | // describe('[id-private-folder] id.genericClaim with backup', () => { 25 | // before(() => backup.getPoWDifficulty().then((res) => { 26 | // // difficulty = res.data.powdifficulty; 27 | // })); 28 | 29 | // before(() => id.authorizeKSignClaim(kc, id.keyOperational, kSign, '', 'appToAuthName', 'authz', 1535208350, 1535208350).then((authRes) => { 30 | // proofOfKSign = authRes.data.proofOfClaim; 31 | // expect(authRes.status).to.be.equal(200); 32 | // })); 33 | 34 | // return id.createId() 35 | // .then(() => id.authorizeKSignClaim(kc, id.keyOperational, proofOfKSign, kSign, 'appToAuthName', 'authz', 1535208350, 1535208350)) 36 | // .then((authRes) => { 37 | // proofOfKSign = authRes.data.proofOfClaim; 38 | // expect(authRes.status).to.be.equal(200); 39 | 40 | // it('id.genericClaim()', () => id.genericClaim(kc, kSign, proofOfKSign, 'iden3.io', 'default', 'extraindex', 'data') 41 | // .then((claimDefRes) => { 42 | // expect(claimDefRes.status).to.be.equal(200); 43 | // })); 44 | // }); 45 | // }); 46 | -------------------------------------------------------------------------------- /test/notification-server.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | const iden3 = require('../index'); 5 | 6 | const relayUrl = 'http://127.0.0.1:8000/api/unstable'; 7 | const nameServerUrl = 'http://127.0.0.1:7000/api/unstable'; 8 | const notificationUrl = 'http://127.0.0.1:10000/api/unstable'; 9 | 10 | describe('[notification-server] Notification server Http communications', () => { 11 | const testName = 'notification-server-test-user'; 12 | let notificationServer; 13 | let id; 14 | let dataBase; 15 | let keyContainer; 16 | let relay; 17 | let proofClaimKeyOperational; 18 | let proofEthName; 19 | let nameServer; 20 | 21 | before('Create servers object', () => { 22 | dataBase = new iden3.Db.LocalStorage('notifdbtest'); 23 | keyContainer = new iden3.KeyContainer(dataBase); 24 | relay = new iden3.Relay(relayUrl); 25 | nameServer = new iden3.NameServer(nameServerUrl); 26 | notificationServer = new iden3.notifications.NotificationServer(notificationUrl); 27 | keyContainer.unlock('pass'); 28 | }); 29 | 30 | after('Lock keyContainer', () => { 31 | keyContainer.lock(); 32 | }); 33 | 34 | it('Generate keys for identity', () => { 35 | const mnemonic = 'clog brass lonely material arrest nominee flight try arrive water life cruise'; 36 | keyContainer.setMasterSeed(mnemonic); 37 | const keys = keyContainer.createKeys(); 38 | // Create identity object 39 | id = new iden3.Id(keys.kOp, keys.kDis, keys.kReen, relay, 0); 40 | }); 41 | 42 | it('Load servers', () => { 43 | id.addNameServer(nameServer); 44 | expect(id.nameServer).to.be.not.equal(undefined); 45 | id.addNotificationServer(notificationServer); 46 | expect(id.nameServer).to.be.not.equal(undefined); 47 | }); 48 | 49 | it('Setup manageNotifications in id', () => { 50 | const discovery = new iden3.discovery.Discovery(iden3.discovery.testEntitiesJSON); 51 | const nameResolver = new iden3.nameResolver.NameResolver(iden3.nameResolver.testNamesJSON); 52 | id.addDiscovery(discovery); 53 | id.addNameResolver(nameResolver); 54 | id.initSignedPacketVerifier(); 55 | id.initManageNotifications(); 56 | }); 57 | 58 | it('Create identity, bind name and get proofClaim of operational key', async () => { 59 | // Create identity 60 | const createIdRes = await id.createId(); 61 | expect(createIdRes.id).to.be.equal(id.id); 62 | expect(createIdRes.id).to.be.not.equal(undefined); 63 | expect(createIdRes.proofClaim).to.be.not.equal(undefined); 64 | proofClaimKeyOperational = createIdRes.proofClaim; 65 | // Bind label to identity address 66 | const bindIdRes = await id.bindId(keyContainer, id.keyOperationalPub, proofClaimKeyOperational, testName); 67 | proofEthName = bindIdRes.data; 68 | }); 69 | 70 | it('Post notification', async () => { 71 | let i = 0; 72 | for (i = 0; i < 10; i++) { 73 | const msg = `dataTest-${i}`; 74 | // Create test notification for a given identity 75 | // eslint-disable-next-line no-await-in-loop 76 | const respPostNot = await id.sendNotification(keyContainer, id.keyOperationalPub, 77 | proofClaimKeyOperational, id.id, notificationUrl, iden3.notifications.newNotifTxt(msg)); 78 | expect(respPostNot.status).to.be.equal(200); 79 | } 80 | }); 81 | 82 | it('Login to notification server', async () => { 83 | // Login to notification server 84 | // It implies: request login packet, sign packet, submit signed packet and receive jws token 85 | const login = await id.loginNotificationServer(proofEthName, keyContainer, id.keyOperationalPub, proofClaimKeyOperational); 86 | expect(login.status).to.be.equal(200); 87 | }); 88 | 89 | 90 | it('Retrieve notifications', async () => { 91 | // Get all 10 notifications 92 | const notifications = await id.getNotifications(); 93 | expect(notifications.length).to.be.equal(10); 94 | let i = 0; 95 | for (i = 0; i < notifications.length; i++) { 96 | const notificationElement = notifications[i]; 97 | const idData = notificationElement.id - 1; 98 | const notificationPost = `dataTest-${idData}`; 99 | expect(notificationElement.data).to.be.equal(notificationPost); 100 | } 101 | // Get notifications before identity notification 5 102 | const notificationsBefore = await id.getNotifications(5, 0); 103 | expect(notificationsBefore.length).to.be.equal(4); 104 | for (i = 0; i < notificationsBefore.length; i++) { 105 | const notificationElement = notificationsBefore[i]; 106 | const idData = notificationElement.id; 107 | expect(idData).to.be.below(5); 108 | } 109 | // Get notifications after identity notification 5 110 | const notificationsAfter = await id.getNotifications(0, 5); 111 | expect(notificationsAfter.length).to.be.equal(5); 112 | for (i = 0; i < notificationsAfter.length; i++) { 113 | const notificationElement = notificationsAfter[i]; 114 | const idData = notificationElement.id; 115 | expect(idData).to.be.above(5); 116 | } 117 | }); 118 | 119 | it('Add 20 notifications in total', async () => { 120 | // Insert 10 more notifications 121 | let i = 0; 122 | for (i = 10; i < 20; i++) { 123 | const notification = `dataTest-${i}`; 124 | // Create test notification for a given identity 125 | // eslint-disable-next-line no-await-in-loop 126 | const respPostNot = await id.sendNotification(keyContainer, id.keyOperationalPub, 127 | proofClaimKeyOperational, id.id, notificationUrl, iden3.notifications.newNotifTxt(notification)); 128 | expect(respPostNot.status).to.be.equal(200); 129 | } 130 | 131 | const notifications = await id.getNotifications(); 132 | expect(notifications.length).to.be.equal(10); 133 | 134 | // Get notifications before id notification 15 135 | // returns 10 notifications before id 15 136 | const notificationsBefore = await id.getNotifications(15, 0); 137 | expect(notificationsBefore.length).to.be.equal(10); 138 | for (i = 0; i < notificationsBefore.length; i++) { 139 | const notificationElement = notificationsBefore[i]; 140 | const idData = notificationElement.id; 141 | expect(idData).to.be.below(15); 142 | } 143 | }); 144 | 145 | it('Delete notifications', async () => { 146 | // Delete notifications 147 | const deleteResp = await id.deleteNotifications(); 148 | expect(deleteResp.status).to.be.equal(200); 149 | expect(deleteResp.data.removed).to.be.equal(20); 150 | // Check pending notifications. Since they have been deleted, it should be 0 151 | const notifications = await id.getNotifications(); 152 | expect(notifications.length).to.be.equal(0); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/private-folder.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { 3 | describe, it, before, after, 4 | } from 'mocha'; 5 | 6 | const chai = require('chai'); 7 | // const childProcess = require('child_process'); 8 | 9 | // const { exec } = childProcess; 10 | const { expect } = chai; 11 | const iden3 = require('../index'); 12 | 13 | // const testPrivKHex = '5ca155481bafd651f6297f525781430e737c3e64a7f854af5870897fa307ae65'; 14 | // const relay = new iden3.Relay('http://127.0.0.1:8000'); 15 | // const relayAddr = '0xe0fbce58cfaa72812103f003adce3f284fe5fc7c'; 16 | const backupUrl = 'http://127.0.0.1:5000/api/unstable'; 17 | const username = 'john@iden3.io'; 18 | const password = 'potatoes'; 19 | 20 | // let id; 21 | // let kSign; 22 | // let proofOfKSign = {}; 23 | 24 | describe('private-folder backup', () => { 25 | const mnemonic = 'enjoy alter satoshi squirrel special spend crop link race rally two eye'; 26 | let dataBase; 27 | let keyContainer; 28 | let backupService; 29 | let backup; 30 | 31 | before('create db and key container', () => { 32 | dataBase = new iden3.Db.LocalStorage(); 33 | keyContainer = new iden3.KeyContainer(dataBase); 34 | keyContainer.unlock('pass'); 35 | keyContainer.generateKeyBackUp(mnemonic); 36 | for (let i = 0; i < 10; i++) { 37 | const key = `key-${i}`; 38 | const value = `value-${i}`; 39 | dataBase.insert(key, value); 40 | } 41 | backup = dataBase.exportWallet(keyContainer); 42 | }); 43 | 44 | before('create backup service and register', async () => { 45 | backupService = new iden3.Backup(backupUrl, username, password); 46 | const resp = await backupService.register(); 47 | expect(resp.status).to.be.equal(200); 48 | }); 49 | 50 | after('lock keyContainer', () => { 51 | keyContainer.lock(); 52 | }); 53 | 54 | it('backup upload', async () => { 55 | const resp = await backupService.upload(backup) 56 | expect(resp.status).to.be.equal(200); 57 | }); 58 | 59 | it('backup download', async () => { 60 | const resp = await backupService.download() 61 | expect(resp.status).to.be.equal(200); 62 | expect(resp.data.backup).to.be.equal(backup); 63 | }); 64 | }); 65 | 66 | // describe('[private-folder] backup.backupData backup.recoverData backup.recoverDataSinceVersion', () => { 67 | // before(() => backup.getPoWDifficulty().then((res) => { 68 | // // const difficulty = res.data.powdifficulty; 69 | // // console.log('difficulty', difficulty); 70 | // })); 71 | // 72 | // before('Delete virtual keyContainer', () => { 73 | // exec('rm -f tmp/*'); 74 | // const key0id = kc.importKey(testPrivKHex); 75 | // id = new iden3.Id(key0id, key0id, key0id, relay, relayAddr, ''); 76 | // kSign = kc.importKey('0dbabbab11336c9f0dfdf583309d56732b1f8a15d52a7412102f49cf5f344d05'); 77 | // }); 78 | // 79 | // it('backupData', () => id.createID().then(() => { 80 | // return id.authorizeKSignClaim(kc, id.keyOperational, {}, kSign, 'appToAuthName', 'authz', 1535208350, 1535208350).then((authRes) => { 81 | // proofOfKSign = authRes.data.proofOfClaim; 82 | // expect(authRes.status).to.be.equal(200); 83 | // 84 | // setTimeout(() => { 85 | // backup.backupData(kc, id.idAddr, kSign, proofOfKSign, 'testtype', 'this is the test data', relayAddr).then((resp) => { 86 | // // console.log("backup", resp.data); 87 | // }); 88 | // }, 100); 89 | // setTimeout(() => { 90 | // backup.backupData(kc, id.idAddr, kSign, proofOfKSign, 'testtype2', 'test data 2', relayAddr).then((resp) => { 91 | // // console.log("backup", resp.data); 92 | // }); 93 | // }, 500); 94 | // 95 | // backup.backupData(kc, id.idAddr, kSign, proofOfKSign, 'testtype', 'test data 3', relayAddr).then((resp) => { 96 | // // console.log("backup", resp.data); 97 | // }); 98 | // setTimeout(() => { 99 | // return backup.backupData(kc, id.idAddr, kSign, proofOfKSign, 'testtype', 'test data 4', relayAddr).then((resp) => { 100 | // 101 | // return backup.recoverData(id.idAddr).then((resp) => { 102 | // const data = resp.data.backups; 103 | // expect(data.length > 3).to.be.equal(true); 104 | // 105 | // console.log('version', backup.version); 106 | // return backup.recoverDataSinceVersion(id.idAddr, backup.version - 1).then((resp) => { 107 | // const data = resp.data.backups; 108 | // // console.log(data); 109 | // // let lastdata = data[0].data; 110 | // // let r = kc.decrypt(lastdata); 111 | // // expect(r).to.be.equal("test data 4"); 112 | // expect(data.length === 1).to.be.equal(true); 113 | // }); 114 | // }); 115 | // }); 116 | // }, 4000); 117 | // }); 118 | // })); 119 | // }); 120 | 121 | // describe('backup.recoverDataByType', () => { 122 | // it('recoverDataByType', () => { 123 | // return backup.recoverDataByType(id.idAddr, 'testtype2').then((resp) => { 124 | // let data = resp.data.backups; 125 | // let lastdata = data[0].Data; 126 | // console.log("l", lastdata); 127 | // let r = kc.decrypt(lastdata); 128 | // expect(r).to.be.equal("test data 2"); 129 | // }); 130 | // }); 131 | // }); 132 | -------------------------------------------------------------------------------- /test/protocols-login.test.js: -------------------------------------------------------------------------------- 1 | import { testNamesJSON } from '../src/api-client/name-resolver'; 2 | import { testEntitiesJSON } from '../src/api-client/discovery'; 3 | 4 | const chai = require('chai'); 5 | 6 | const { expect } = chai; 7 | 8 | const iden3 = require('../index'); 9 | 10 | const relayKSignPk = '117f0a278b32db7380b078cdb451b509a2ed591664d1bac464e8c35a90646796'; 11 | const name = 'protocols-login-test-user'; 12 | const mnemonic = 'adjust toy select soon nest caught resource rally side wheat traffic amount'; 13 | 14 | const db = new iden3.Db.LocalStorage('protocoldbtest'); 15 | const kc = new iden3.KeyContainer(db); 16 | kc.unlock('pass'); 17 | const relay = new iden3.Relay('http://127.0.0.1:8000/api/unstable'); 18 | const nameServerUrl = 'http://127.0.0.1:7000/api/unstable'; 19 | const nameServer = new iden3.NameServer(nameServerUrl); 20 | 21 | kc.setMasterSeed(mnemonic); 22 | const keys = kc.createKeys(); 23 | const id = new iden3.Id(keys.kOp, keys.kDis, keys.kReen, relay, 0); 24 | id.addNameServer(nameServer); 25 | const ksign = keys.kOp; // public key in hex format 26 | 27 | // vars that will be filled with http requests to the relay 28 | let proofEthName = {}; 29 | let proofKSign = {}; 30 | 31 | describe('[protocol] login', () => { 32 | let signedPacketVerifier; 33 | 34 | before('Create idenity and bind it to a name', async () => { 35 | kc.unlock('pass'); 36 | const resCreateId = await id.createId(); 37 | proofKSign = resCreateId.proofClaim; 38 | const resBindId = await id.bindId(kc, id.keyOperationalPub, proofKSign, name); 39 | proofEthName = resBindId.data; 40 | const resResolveName = await nameServer.resolveName(`${name}@iden3.eth`); 41 | expect(resResolveName.data.id).to.be.equal(id.id); 42 | }); 43 | 44 | after('lock key container', () => { 45 | kc.lock(); 46 | }); 47 | 48 | it('initialize login objects', () => { 49 | const discovery = new iden3.discovery.Discovery(testEntitiesJSON); 50 | const nameResolver = new iden3.nameResolver.NameResolver(testNamesJSON); 51 | signedPacketVerifier = new iden3.protocols.login.SignedPacketVerifier(discovery, nameResolver); 52 | }); 53 | 54 | it('verify ProofClaimFull (proofClaimAssignName & proofKSign)', () => { 55 | const ksignVerified = iden3.protocols.verifyProofClaimFull(proofKSign, relayKSignPk); 56 | expect(ksignVerified).to.be.equal(true); 57 | 58 | const assignNameVerified = iden3.protocols.verifyProofClaimFull(proofEthName.proofAssignName, relayKSignPk); 59 | expect(assignNameVerified).to.be.equal(true); 60 | }); 61 | 62 | it('newRequestIdenAssert', () => { 63 | const origin = 'domain.io'; 64 | const nonceDB = new iden3.protocols.nonceDB.NonceDB(); 65 | const signatureRequest = iden3.protocols.login.newRequestIdenAssert(nonceDB, origin, 2 * 60); 66 | 67 | const date = new Date(); 68 | const unixtime = Math.round((date).getTime() / 1000); 69 | const resClaim = iden3.protocols.verifyProofClaimFull(proofKSign, relayKSignPk); 70 | expect(resClaim).to.be.equal(true); 71 | 72 | const expirationTime = unixtime + (3600 * 60); 73 | const signedPacket = iden3.protocols.login.signIdenAssertV01(signatureRequest, 74 | id.id, proofEthName, kc, ksign, proofKSign, expirationTime); 75 | 76 | const resIdenAssert = signedPacketVerifier.verifySignedPacketIdenAssert(signedPacket, nonceDB, origin); 77 | expect(resIdenAssert).to.be.not.equal(undefined); 78 | // check that the nonce returned when deleting the nonce of the signedPacket, is the same 79 | // than the nonce of the signatureRequest 80 | expect(resIdenAssert.nonceObj.nonce).to.be.equal(signatureRequest.body.data.challenge); 81 | // nonce must not be more in the nonceDB 82 | expect(nonceDB.search(resIdenAssert.nonceObj.nonce)).to.be.equal(undefined); 83 | expect(resIdenAssert.ethName).to.be.equal(proofEthName.ethName); 84 | expect(resIdenAssert.id).to.be.equal(id.id); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/recv-notifications.test.js: -------------------------------------------------------------------------------- 1 | // This test requires a first step from go-iden3: 2 | // github.com/iden3/go-iden3/services/notificationsrv TestInt 3 | 4 | const chai = require('chai'); 5 | 6 | const { expect } = chai; 7 | const iden3 = require('../index'); 8 | 9 | const relayUrl = 'http://127.0.0.1:8000/api/unstable'; 10 | const nameServerUrl = 'http://127.0.0.1:7000/api/unstable'; 11 | const notificationUrl = 'http://127.0.0.1:10000/api/unstable'; 12 | 13 | describe('[notification-server] Notification server Http communications', () => { 14 | const testName = 'recv-notifications-test-user'; 15 | let notificationServer; 16 | let id; 17 | let dataBase; 18 | let keyContainer; 19 | let relay; 20 | let proofClaimKeyOperational; 21 | let proofEthName; 22 | let nameServer; 23 | 24 | before('Create servers object', () => { 25 | dataBase = new iden3.Db.LocalStorage('recvnottest'); 26 | keyContainer = new iden3.KeyContainer(dataBase); 27 | relay = new iden3.Relay(relayUrl); 28 | nameServer = new iden3.NameServer(nameServerUrl); 29 | notificationServer = new iden3.notifications.NotificationServer(notificationUrl); 30 | console.log("Unlocking key container..."); 31 | keyContainer.unlock('pass'); 32 | }); 33 | 34 | after('Lock keyContainer', () => { 35 | console.log("Locking key container..."); 36 | keyContainer.lock(); 37 | }) 38 | 39 | it('Generate keys for identity', () => { 40 | // https://iancoleman.io/bip39/ 41 | const mnemonic = 'emerge resource veteran where letter quantum budget elite bracket grab pioneer plunge'; 42 | keyContainer.setMasterSeed(mnemonic); 43 | const keys = keyContainer.createKeys(); 44 | // Create identity object 45 | // console.log('kOp', keys.kOp); 46 | id = new iden3.Id(keys.kOp, keys.kDis, keys.kReen, relay, 0); 47 | }); 48 | 49 | it('Load servers', () => { 50 | id.addNameServer(nameServer); 51 | expect(id.nameServer).to.be.not.equal(undefined); 52 | id.addNotificationServer(notificationServer); 53 | expect(id.nameServer).to.be.not.equal(undefined); 54 | }); 55 | 56 | it('Setup manageNotifications in id', () => { 57 | const discovery = new iden3.discovery.Discovery(iden3.discovery.testEntitiesJSON); 58 | const nameResolver = new iden3.nameResolver.NameResolver(iden3.nameResolver.testNamesJSON); 59 | id.addDiscovery(discovery); 60 | id.addNameResolver(nameResolver); 61 | id.initSignedPacketVerifier(); 62 | id.initManageNotifications(); 63 | }); 64 | 65 | it('Create identity, bind name and get proofClaim of operational key', async () => { 66 | // Create identity 67 | const createIdRes = await id.createId(); 68 | // console.log('id', id.id); 69 | expect(createIdRes.id).to.be.equal(id.id); 70 | expect(createIdRes.id).to.be.not.equal(undefined); 71 | expect(createIdRes.proofClaim).to.be.not.equal(undefined); 72 | proofClaimKeyOperational = createIdRes.proofClaim; 73 | // Bind label to identity address 74 | const bindIdRes = await id.bindId(keyContainer, id.keyOperationalPub, proofClaimKeyOperational, testName); 75 | proofEthName = bindIdRes.data; 76 | }); 77 | 78 | it('Login to notification server', async () => { 79 | // Login to notification server 80 | // It implies: request login packet, sign packet, submit signed packet and receive jws token 81 | const login = await id.loginNotificationServer(proofEthName, keyContainer, id.keyOperationalPub, proofClaimKeyOperational); 82 | expect(login.status).to.be.equal(200); 83 | }); 84 | 85 | 86 | it('Retrieve notifications', async () => { 87 | // Get all 10 notifications 88 | const notifications = await id.getNotifications(); 89 | expect(notifications.length).to.be.equal(2); 90 | expect(notifications[0].type).to.be.equal(iden3.notifications.NOTIFTXTV01); 91 | expect(notifications[0].data).to.be.equal('notificationText'); 92 | expect(notifications[1].type).to.be.equal(iden3.notifications.NOTIFCLAIMV01); 93 | }); 94 | }); 95 | --------------------------------------------------------------------------------