├── data
├── .gitignore
└── origin-header.png
├── .travis.yml
├── public
├── favicon.ico
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── images
│ └── origin-logo-dark.png
├── dev.html
├── index.html
└── vendor
│ └── driver.min.css
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── src
├── utils
│ ├── keyMirror.js
│ ├── numberFormat.js
│ ├── decodeFn.js
│ ├── balance.js
│ └── ipfsHash.js
├── components
│ ├── FormRow.js
│ ├── NavLink.js
│ ├── NavItem.js
│ ├── Loading.js
│ ├── DetailRow.js
│ ├── Dropdown.js
│ ├── AccountChooser.js
│ └── Modal.js
├── index.js
├── Store.js
├── pages
│ ├── _Versions.js
│ ├── identity
│ │ ├── _WalletChooser.js
│ │ ├── modals
│ │ │ ├── _RemoveIdentity.js
│ │ │ ├── _Approve.js
│ │ │ ├── _RemoveKey.js
│ │ │ ├── _RemoveClaim.js
│ │ │ ├── KeyDetail.js
│ │ │ ├── _CheckClaim.js
│ │ │ ├── _NewVerifier.js
│ │ │ ├── _AddKey.js
│ │ │ ├── ClaimDetail.js
│ │ │ └── _NewClaimIssuer.js
│ │ ├── Home.js
│ │ ├── _Events.js
│ │ ├── _ValidateClaim.js
│ │ └── ClaimCheckerDetail.js
│ ├── _driver.js
│ ├── console
│ │ ├── _IPFS.js
│ │ ├── index.js
│ │ └── _Providers.js
│ ├── _Init.js
│ └── App.js
├── constants
│ └── Providers.js
├── reducers
│ ├── Network.js
│ ├── Wallet.js
│ └── Identity.js
├── actions
│ ├── Network.js
│ └── Wallet.js
└── contracts
│ └── ClaimVerifier.js
├── .gitignore
├── scripts
├── deploy.js
└── accounts.js
├── issuer-services
├── package.json
├── index.js
├── html.js
├── app.js
├── config.json.eg
├── standalone.js
├── _simple.js
├── _github.js
├── _google.js
├── _linkedin.js
├── _twitter.js
└── _facebook.js
├── webpack.prod.js
├── LICENSE
├── webpack.config.js
├── contracts
├── ERC725.sol
├── ERC735.sol
├── Identity.sol
├── ClaimVerifier.sol
├── ClaimHolder.sol
└── KeyHolder.sol
├── index.js
├── test
├── ClaimVerifier.js
├── _helper.js
└── Identity.js
├── package.json
└── README.md
/data/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "lts/*"
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/data/origin-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/data/origin-header.png
--------------------------------------------------------------------------------
/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/public/images/origin-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/images/origin-logo-dark.png
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriginProtocol/origin-playground/HEAD/public/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | If you need help, please ask in our [#engineering channel on Discord](http://www.originprotocol.com/discord).
2 |
3 | We use issues for tracking features, bugs and discussions.
--------------------------------------------------------------------------------
/src/utils/keyMirror.js:
--------------------------------------------------------------------------------
1 | export default function(obj, namespace) {
2 | var ret = {},
3 | key
4 |
5 | for (key in obj) {
6 | if (namespace) {
7 | ret[key] = namespace + '_' + key
8 | } else {
9 | ret[key] = key
10 | }
11 | }
12 | return ret
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | public/app*.js
4 | public/vendor*.js
5 | public/css/app.css
6 | data/db
7 | data/ipfs
8 | data/OfficialIdentities.js
9 | NOTES
10 | src/contracts/deployed.js
11 | issuer-services/package-lock.json
12 | issuer-services/up.json
13 | issuer-services/config.json
14 |
--------------------------------------------------------------------------------
/src/components/FormRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FormRow = props => (
4 |
(
6 |
7 |
8 |
9 | )}/>
10 | )
11 |
12 | export default NavLink
13 |
--------------------------------------------------------------------------------
/src/components/NavItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const NavItem = props => (
4 |
5 | {
9 | e.preventDefault()
10 | props.onClick(props.id)
11 | }}
12 | >
13 | {props.label}
14 |
15 |
16 | )
17 |
18 | export default NavItem
19 |
--------------------------------------------------------------------------------
/src/utils/numberFormat.js:
--------------------------------------------------------------------------------
1 | export default function numberFormat(number, dec, dsep, tsep) {
2 | if (isNaN(number) || number == null) return ''
3 |
4 | number = number.toFixed(~~dec)
5 | tsep = typeof tsep == 'string' ? tsep : ','
6 |
7 | var parts = number.split('.'),
8 | fnums = parts[0],
9 | decimals = parts[1] ? (dsep || '.') + parts[1] : ''
10 |
11 | return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + tsep) + decimals
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/decodeFn.js:
--------------------------------------------------------------------------------
1 | import web3 from 'Web3'
2 |
3 | export default function decodeFn(Contract, data) {
4 | var methodSigs = Contract.abi.filter(a => a.type === 'function').reduce((m, fn) => {
5 | m[web3.eth.abi.encodeFunctionSignature(fn)] = fn
6 | return m
7 | }, {})
8 |
9 | var methodAbi = methodSigs[data.slice(0, 10)]
10 | return { name: methodAbi.name, params: web3.eth.abi.decodeParameters(methodAbi.inputs, data.slice(10)) }
11 | }
12 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | import helper from '../test/_helper'
2 |
3 | ;(async () => {
4 | var { accounts, web3, deploy, server } = await helper(
5 | `${__dirname}/identity/`,
6 | 'https://rinkeby.infura.io'
7 | // 'http://localhost:8545'
8 | )
9 |
10 | var account = web3.eth.accounts.wallet.add(
11 | '0xPRIV_KEY'
12 | )
13 |
14 | await deploy('ClaimHolder', { from: account, log: true })
15 |
16 | if (server) {
17 | server.close()
18 | }
19 | })()
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { Route, HashRouter } from 'react-router-dom'
5 |
6 | import Store from './Store'
7 | import App from './pages/App'
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('app')
16 | )
17 |
18 | require('react-styl').addStylesheet()
19 |
--------------------------------------------------------------------------------
/issuer-services/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "issuer-services",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node standalone.js"
9 | },
10 | "author": "",
11 | "license": "MIT",
12 | "dependencies": {
13 | "express": "^4.16.3",
14 | "express-session": "^1.15.6",
15 | "oauth": "^0.9.15",
16 | "superagent": "^3.8.2",
17 | "ws": "^5.1.1",
18 | "xhr2": "^0.1.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/balance.js:
--------------------------------------------------------------------------------
1 | import numberFormat from './numberFormat'
2 |
3 | export default function(wei, exchangeRates) {
4 | var eth = web3.utils.fromWei(wei, 'ether')
5 | var balance = {
6 | wei,
7 | eth,
8 | ethStr: `${numberFormat(Number(eth), 4)} ETH`
9 | }
10 | Object.keys(exchangeRates).forEach(currency => {
11 | balance[currency] =
12 | Math.round(exchangeRates[currency] * Number(eth) * 100) / 100
13 | balance[currency + 'Str'] = '$' + numberFormat(balance[currency], 2)
14 | })
15 | return balance
16 | }
17 |
--------------------------------------------------------------------------------
/issuer-services/index.js:
--------------------------------------------------------------------------------
1 | var Web3 = require('web3')
2 |
3 | var Config = require('./config.json')
4 | var facebook = require('./_facebook')
5 | var google = require('./_google')
6 | var github = require('./_github')
7 | var simple = require('./_simple')
8 | var twitter = require('./_twitter')
9 | var linkedin = require('./_linkedin')
10 |
11 | module.exports = function (app) {
12 | Config.web3 = new Web3()
13 | facebook(app, Config)
14 | google(app, Config)
15 | github(app, Config)
16 | simple(app, Config)
17 | twitter(app, Config)
18 | linkedin(app, Config)
19 | }
20 |
--------------------------------------------------------------------------------
/src/Store.js:
--------------------------------------------------------------------------------
1 | import thunkMiddleware from 'redux-thunk'
2 | import { createStore, applyMiddleware, combineReducers } from 'redux'
3 |
4 | import network from './reducers/Network'
5 | import wallet from './reducers/Wallet'
6 | import identity from './reducers/Identity'
7 |
8 | let middlewares = [thunkMiddleware]
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | const { logger } = require(`redux-logger`)
12 | middlewares.push(logger)
13 | }
14 |
15 | export default createStore(
16 | combineReducers({
17 | wallet,
18 | network,
19 | identity
20 | }),
21 | applyMiddleware(...middlewares)
22 | )
23 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | First pull request? Read our [guide to contributing](http://docs.originprotocol.com/#contributing)
2 |
3 | ### Checklist:
4 |
5 | - [ ] Code contains relevant tests for the problem you are solving
6 | - [ ] Ensure all new and existing tests pass
7 | - [ ] Update any relevant READMEs and [docs](https://github.com/OriginProtocol/docs)
8 |
9 | ### Description:
10 |
11 | Please explain the changes you made here:
12 |
13 | - A description of the problem you're trying to solve
14 | - An overview of the suggested solution
15 | - If the feature changes current behavior, reasons why your solution is better
--------------------------------------------------------------------------------
/public/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ERC 725 - dev
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Loading = props => {
4 | if (props.show !== true) return null
5 | return (
6 |
7 |
8 | Loading...
9 |
10 |
11 | )
12 | }
13 |
14 | export default Loading
15 |
16 | require('react-styl')(`
17 | .loading-spinner
18 | z-index: 5
19 | position: absolute;
20 | background: rgba(255,255,255,0.9);
21 | top: 0;
22 | left: 0;
23 | right: 0;
24 | bottom: 0;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | font-size: 1.5rem;
29 | color: #333;
30 | `)
31 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ERC 725
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/pages/_Versions.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Dropdown from 'components/Dropdown'
4 |
5 | const latestVersion = '0.1.0'
6 | const oldVersions = [
7 | // { version: '0.1.0', hash: '' }
8 | ]
9 |
10 | const Versions = () => (
11 |
12 |
13 | {latestVersion}
14 |
15 |
16 | {oldVersions.map(({ version, hash }) => (
17 | {}}
22 | >
23 | {version}
24 |
25 |
26 | ))}
27 |
28 | )
29 |
30 | export default Versions
31 |
--------------------------------------------------------------------------------
/src/pages/identity/_WalletChooser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class WalletChooser extends Component {
4 | render() {
5 | return (
6 |
7 |
Active wallet:
8 |
9 | {this.props.wallet.accounts.map((a, idx) => (
10 | this.props.selectAccount(a)}
16 | >
17 | {a.substr(0, 6)}
18 |
19 | ))}
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default WalletChooser
27 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var devConfig = require('./webpack.config.js')
3 | var path = require('path')
4 |
5 | var prodConfig = {
6 | ...devConfig,
7 | mode: 'production',
8 | devtool: false
9 | }
10 |
11 | prodConfig.plugins.push(new webpack.IgnorePlugin(/redux-logger/))
12 | prodConfig.resolve.alias = {
13 | react: 'react/umd/react.production.min.js',
14 | 'react-dom': 'react-dom/umd/react-dom.production.min.js',
15 | 'react-styl': 'react-styl/prod.js',
16 | web3: path.resolve(__dirname, 'public/web3.min'),
17 | redux: 'redux/dist/redux.min.js',
18 | 'react-redux': 'react-redux/dist/react-redux.min.js',
19 | 'react-router-dom': 'react-router-dom/umd/react-router-dom.min.js'
20 | }
21 | prodConfig.module.noParse = [
22 | /^(react|react-dom|react-styl|redux|react-redux|react-router-dom)$/,
23 | /web3/
24 | ]
25 |
26 | module.exports = prodConfig
27 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveIdentity.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 |
5 | class RemoveIdentity extends Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {}
9 | }
10 |
11 | render() {
12 | return (
13 | this.props.onClose()}
16 | >
17 |
18 |
Are you sure you wish to remove this identity?
19 |
20 | {
23 | this.props.removeIdentity()
24 | }}
25 | >
26 | Remove
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | export default RemoveIdentity
36 |
--------------------------------------------------------------------------------
/src/constants/Providers.js:
--------------------------------------------------------------------------------
1 | const HOST = process.env.HOST || 'localhost'
2 |
3 | export default [
4 | {
5 | name: 'Localhost',
6 | endpoints: [`http://${HOST}:8545`, `ws://${HOST}:8545`]
7 | },
8 | {
9 | name: 'Ropsten',
10 | endpoints: ['https://ropsten.infura.io', 'wss://ropsten.infura.io/ws'],
11 | faucet: 'http://faucet.ropsten.be:3001'
12 | },
13 | {
14 | name: 'Rinkeby',
15 | endpoints: ['https://rinkeby.infura.io', 'wss://rinkeby.infura.io/ws'],
16 | faucet: 'https://faucet.rinkeby.io'
17 | },
18 | {
19 | name: 'Mainnet',
20 | endpoints: ['https://mainnet.infura.io', 'wss://mainnet.infura.io/ws']
21 | },
22 | {
23 | name: 'Kovan',
24 | endpoints: ['https://kovan.infura.io'],
25 | website: 'https://kovan-testnet.github.io/website',
26 | faucet: 'https://github.com/kovan-testnet/faucet'
27 | }
28 | // {
29 | // name: 'Custom'
30 | // }
31 | ]
32 |
--------------------------------------------------------------------------------
/issuer-services/html.js:
--------------------------------------------------------------------------------
1 | const HTML = content => `
2 |
3 |
4 | ERC 725 - Auth
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 | ${content}
26 | `
27 |
28 | module.exports = HTML
29 |
--------------------------------------------------------------------------------
/src/utils/ipfsHash.js:
--------------------------------------------------------------------------------
1 | // export function getBytes32FromIpfsHash(hash) {
2 | // return hash;
3 | // }
4 | // export function getIpfsHashFromBytes32(bytes32Hex) {
5 | // return bytes32Hex;
6 | // }
7 | import bs58 from 'bs58'
8 |
9 | export function getBytes32FromIpfsHash(hash) {
10 | return `0x${bs58
11 | .decode(hash)
12 | .slice(2)
13 | .toString('hex')}`
14 | }
15 |
16 | // Return base58 encoded ipfs hash from bytes32 hex string,
17 | // E.g. "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
18 | // --> "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL"
19 | export function getIpfsHashFromBytes32(bytes32Hex) {
20 | // Add our default ipfs values for first 2 bytes:
21 | // function:0x12=sha2, size:0x20=256 bits
22 | // and cut off leading "0x"
23 | const hashHex = '1220' + bytes32Hex.slice(2)
24 | const hashBytes = Buffer.from(hashHex, 'hex')
25 | const hashStr = bs58.encode(hashBytes)
26 | return hashStr
27 | }
28 |
--------------------------------------------------------------------------------
/scripts/accounts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a list of accounts and private keys given a mnemonic
3 | */
4 |
5 | var bip39 = require('bip39')
6 | var HDKey = require('hdkey')
7 | var Web3 = require('web3')
8 | var web3 = new Web3()
9 |
10 | const mnemonic = process.argv.slice(2).join(' ')
11 |
12 | if (!mnemonic) {
13 | console.log("\nUsage: node accounts.js [mnemonic]")
14 | console.log("eg node accounts.js candy maple cake sugar pudding cream honey rich smooth crumble sweet treat\n")
15 | process.exit()
16 | }
17 |
18 | console.log(`\nMnemonic: ${mnemonic}\n`)
19 |
20 | for (var offset = 0; offset < 10; offset++) {
21 | var seed = bip39.mnemonicToSeed(mnemonic)
22 | var acct = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/" + offset)
23 | var account = web3.eth.accounts.privateKeyToAccount(
24 | `0x${acct.privateKey.toString('hex')}`
25 | )
26 | console.log(`${account.address} - ${account.privateKey}`)
27 | }
28 |
29 | console.log()
30 | process.exit()
31 |
--------------------------------------------------------------------------------
/src/pages/_driver.js:
--------------------------------------------------------------------------------
1 |
2 | var driver = new Driver();
3 | window.driver = driver;
4 | // Define the steps for introduction
5 | driver.defineSteps([
6 | {
7 | element: '.wallet-chooser',
8 | popover: {
9 | title: 'Wallet Chooser',
10 | description: 'The wallet chooser lets you quickly switch between wallets. Use different wallets for Identities, Certifiers and Protected Contracts.',
11 | position: 'bottom'
12 | }
13 | },
14 | {
15 | element: '.identities-list',
16 | popover: {
17 | title: 'Identities',
18 | description: 'Identities have Claims and',
19 | position: 'bottom'
20 | }
21 | },
22 | {
23 | element: '.certifiers-list',
24 | popover: {
25 | title: 'Title on Popover',
26 | description: 'Body of the popover',
27 | position: 'top'
28 | }
29 | },
30 | {
31 | element: '.protected-list',
32 | popover: {
33 | title: 'Title on Popover',
34 | description: 'Body of the popover',
35 | position: 'right'
36 | }
37 | },
38 | ]);
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Origin Protocol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/issuer-services/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var session = require('express-session')
3 | var Web3 = require('web3')
4 |
5 | var simple = require('./_simple')
6 | var facebook = require('./_facebook')
7 | var twitter = require('./_twitter')
8 | var github = require('./_github')
9 | var google = require('./_google')
10 | var linkedin = require('./_linkedin')
11 |
12 | try {
13 | var Config = require('./config.json')
14 | } catch (e) {
15 | console.log('Please copy config.json.eg to config.json and update it.')
16 | process.exit()
17 | }
18 |
19 | Config.web3 = new Web3(Config.provider)
20 |
21 | const app = express()
22 | app.use(
23 | session({
24 | secret: 'top secret string',
25 | resave: false,
26 | saveUninitialized: true
27 | })
28 | )
29 |
30 | app.get('/', (req, res) => {
31 | res.send('Issuer Services')
32 | })
33 |
34 | simple(app, Config)
35 | facebook(app, Config)
36 | twitter(app, Config)
37 | github(app, Config)
38 | google(app, Config)
39 | linkedin(app, Config)
40 |
41 | const PORT = process.env.PORT || 3001
42 | app.listen(PORT, () => {
43 | console.log(`\nListening on port ${PORT}\n`)
44 | })
45 |
--------------------------------------------------------------------------------
/src/pages/identity/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class Home extends Component {
4 |
5 | render() {
6 | return (
7 |
8 |
9 | Select a contract for more
10 | information
11 |
12 |
13 |
14 |
Identity
15 | Controlled by Keys. Has Claims, can add Claims to other identities.
16 |
17 |
18 |
Claim Issuer
19 | Also an Identity. Trusted by Claim Checkers to issue valid claims.
20 |
21 |
22 |
Claim Checker
23 | A contract only allowing interactions from Identites holding Claims
24 | from a Trusted Issuer.
25 |
26 |
27 |
Claim
28 | Some data on one Identity that provably came from another Identity.
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | export default Home
36 |
--------------------------------------------------------------------------------
/issuer-services/config.json.eg:
--------------------------------------------------------------------------------
1 | {
2 | "provider": "ws://localhost:8545",
3 | "baseUrl": "http://localhost:3000",
4 | "facebookApp": {
5 | "client_id": "000000000000000",
6 | "secret": "00000000000000000000000000000000",
7 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
8 | },
9 | "twitterApp": {
10 | "client_id": "0000000000000000000000000",
11 | "secret": "00000000000000000000000000000000000000000000000000",
12 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
13 | },
14 | "githubApp": {
15 | "client_id": "00000000000000000000",
16 | "secret": "0000000000000000000000000000000000000000",
17 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
18 | },
19 | "googleApp": {
20 | "client_id": "0000000000000000000000000.apps.googleusercontent.com",
21 | "secret": "000000000000000000000000",
22 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
23 | },
24 | "linkedInApp": {
25 | "client_id": "00000000000000",
26 | "secret": "0000000000000000",
27 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
28 | },
29 | "simpleApp": {
30 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/console/_IPFS.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { setIpfs } from 'actions/Network'
5 |
6 | const HOST = process.env.HOST || 'localhost'
7 |
8 | class IPFS extends Component {
9 | render() {
10 | return (
11 |
12 | IPFS:
13 |
14 |
17 | this.props.setIpfs(`http://${HOST}:9090`, `http://${HOST}:5002`)
18 | }
19 | >
20 | Localhost
21 |
22 |
25 | this.props.setIpfs('https://gateway.originprotocol.com', 'https://gateway.originprotocol.com')
26 | }
27 | >
28 | gateway.originprotocol.com
29 |
30 | ipfs.infura.io
31 | gateway.ipfs.io
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | // const mapStateToProps = state => ({
39 | // })
40 |
41 | const mapDispatchToProps = dispatch => ({
42 | setIpfs: (gateway, api) => dispatch(setIpfs(gateway, api))
43 | })
44 |
45 | export default connect(null, mapDispatchToProps)(IPFS)
46 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | var OfficialIdentities = []
5 | try {
6 | OfficialIdentities = require('./data/OfficialIdentities')
7 | } catch (e) {
8 | /* Ignore */
9 | }
10 |
11 | var config = {
12 | entry: {
13 | app: './src/index.js'
14 | },
15 | devtool: 'cheap-module-source-map',
16 | output: {
17 | filename: '[name].js',
18 | path: path.resolve(__dirname, 'public'),
19 | hashDigestLength: 8
20 | },
21 | externals: {
22 | Web3: 'web3'
23 | },
24 | module: {
25 | noParse: [/^react$/],
26 | rules: [
27 | {
28 | test: /\.js$/,
29 | exclude: /node_modules/,
30 | loader: 'babel-loader'
31 | }
32 | ]
33 | },
34 | resolve: {
35 | extensions: ['.js', '.json']
36 | },
37 | node: {
38 | fs: 'empty'
39 | },
40 | devServer: {
41 | port: 8081,
42 | headers: {
43 | 'Access-Control-Allow-Origin': '*'
44 | }
45 | },
46 | mode: 'development',
47 | plugins: [
48 | new webpack.EnvironmentPlugin({ HOST: 'localhost' }),
49 | new webpack.DefinePlugin({
50 | OfficialIdentities: JSON.stringify(OfficialIdentities)
51 | })
52 | ],
53 |
54 | optimization: {
55 | splitChunks: {
56 | cacheGroups: {
57 | vendor: {
58 | chunks: 'initial',
59 | test: path.resolve(__dirname, 'node_modules'),
60 | name: 'vendor',
61 | enforce: true
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | module.exports = config
69 |
--------------------------------------------------------------------------------
/issuer-services/standalone.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const session = require('express-session')
3 | const crypto = require('crypto')
4 | const WebSocket = require('ws')
5 | const serveStatic = require('serve-static')
6 |
7 | global.XMLHttpRequest = require('xhr2')
8 | global.window = { Promise, WebSocket, crypto }
9 |
10 | var simple = require('./_simple')
11 | var facebook = require('./_facebook')
12 | var google = require('./_google')
13 | var github = require('./_github')
14 | var twitter = require('./_twitter')
15 | var linkedin = require('./_linkedin')
16 |
17 | var Web3 = require('./public/vendor/web3.min')
18 |
19 | let Config = require('./config.json')
20 | Config.web3 = new Web3(Config.provider)
21 |
22 | const app = express()
23 | if (process.env.NODE_ENV === 'production') {
24 | app.use((req, res, next) => {
25 | if (req.header('x-forwarded-proto') !== 'https') {
26 | res.redirect(`https://${req.header('host')}${req.url}`)
27 | } else {
28 | next()
29 | }
30 | })
31 | }
32 | app.use(serveStatic('public'))
33 | app.use(
34 | session({
35 | secret: 'top secret string',
36 | resave: false,
37 | saveUninitialized: true
38 | })
39 | )
40 |
41 | app.get('/', (req, res) => {
42 | res.sendFile(__dirname + '/public/index.html')
43 | })
44 |
45 | simple(app, Config)
46 | facebook(app, Config)
47 | google(app, Config)
48 | github(app, Config)
49 | twitter(app, Config)
50 | linkedin(app, Config)
51 |
52 | const PORT = process.env.PORT || 3001
53 | app.listen(PORT, () => {
54 | console.log(`\nListening on port ${PORT}\n`)
55 | })
56 |
--------------------------------------------------------------------------------
/src/pages/identity/_Events.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ClaimHolder from '../../contracts/ClaimHolder'
4 | import decodeFn from 'utils/decodeFn'
5 |
6 | const Events = props => {
7 | if (props.eventsResponse === null) {
8 | return Loading...
9 | }
10 | return (
11 |
12 | {props.events.length ? null :
No Events
}
13 | {props.events.map((event, idx) => {
14 | var data = null
15 | if (String(event.event).match(/(ExecutionRequested|Executed)/)) {
16 | var decoded = decodeFn(ClaimHolder, event.returnValues.data)
17 | data = (
18 |
19 | {`${decoded.name}\n`}
20 | {displayEvent(decoded.params)}
21 |
22 | )
23 | }
24 |
25 | return (
26 |
27 |
28 | {event.event}
29 | Block {event.blockNumber}
30 |
31 |
{displayEvent(event.returnValues)}
32 | {data}
33 |
34 | )
35 | })}
36 |
37 | )
38 | }
39 |
40 | export function displayEvent(obj) {
41 | if (typeof obj !== 'object') {
42 | return ''
43 | }
44 | var ret = []
45 | Object.keys(obj).forEach(key => {
46 | if (!key.match(/^([0-9]+|__.*)$/)) {
47 | var val = String(obj[key])
48 | val = val.length > 32 ? `${val.substr(0, 32)}...` : val
49 | ret.push(`${key}: ${val}`)
50 | }
51 | })
52 | return ret.join('\n')
53 | }
54 |
55 | export default Events
56 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_Approve.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class NewIdentity extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
32 | >
33 |
34 | Are you sure?
35 |
36 | {
39 | this.props.approveExecution(
40 | this.props.identity,
41 | this.props.executionId
42 | )
43 | }}
44 | >
45 | Approve
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | export default NewIdentity
54 |
--------------------------------------------------------------------------------
/issuer-services/_simple.js:
--------------------------------------------------------------------------------
1 | var HTML = require('./html')
2 |
3 | const ClaimType = '7';
4 |
5 | module.exports = function dummyService(app, { web3, simpleApp }) {
6 |
7 | app.get('/simple-auth', async (req, res) => {
8 | var issuer = req.query.issuer,
9 | target = req.query.target
10 |
11 | if (!target) {
12 | res.send(HTML('No target identity contract provided'))
13 | return
14 | }
15 | if (!issuer) {
16 | res.send(HTML('No issuer identity contract provided'))
17 | return
18 | }
19 | if (!simpleApp.claimSignerKey) {
20 | res.send(HTML('No private key specified.'))
21 | return
22 | }
23 |
24 | var rawData = 'Verified OK'
25 | var hexData = web3.utils.asciiToHex(rawData)
26 | var hashed = web3.utils.soliditySha3(target, ClaimType, hexData)
27 | var signedData = await web3.eth.accounts.sign(hashed, simpleApp.claimSignerKey)
28 |
29 | res.send(
30 | HTML(
31 | `This example authentication service returns some signed data which can be added to a claim
32 | Issuer: ${issuer}
33 | Target: ${target}
34 | Data: ${rawData}
35 | Signature: ${signedData.signature}
36 | Hash: ${signedData.messageHash}
37 | OK
38 | `
45 | )
46 | )
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/contracts/ERC725.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | // **Warning!** This file is a protoype version of our work around ERC 725.
4 | // This file is now out of date and **should not be used**.
5 | // Our current identity contracts are here:
6 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
7 |
8 | contract ERC725 {
9 |
10 | uint256 constant MANAGEMENT_KEY = 1;
11 | uint256 constant ACTION_KEY = 2;
12 | uint256 constant CLAIM_SIGNER_KEY = 3;
13 | uint256 constant ENCRYPTION_KEY = 4;
14 |
15 | event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
16 | event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
17 | event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
18 | event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
19 | event Approved(uint256 indexed executionId, bool approved);
20 |
21 | struct Key {
22 | uint256 purpose; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc.
23 | uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc.
24 | bytes32 key;
25 | }
26 |
27 | function getKey(bytes32 _key) public constant returns(uint256 purpose, uint256 keyType, bytes32 key);
28 | function getKeyPurpose(bytes32 _key) public constant returns(uint256 purpose);
29 | function getKeysByPurpose(uint256 _purpose) public constant returns(bytes32[] keys);
30 | function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success);
31 | function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId);
32 | function approve(uint256 _id, bool _approve) public returns (bool success);
33 | }
34 |
--------------------------------------------------------------------------------
/contracts/ERC735.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | // **Warning!** This file is a protoype version of our work around ERC 725.
4 | // This file is now out of date and **should not be used**.
5 | // Our current identity contracts are here:
6 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
7 |
8 | contract ERC735 {
9 |
10 | event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes32 signature, bytes claim, string uri);
11 | event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
12 | event ClaimRemoved(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
13 | event ClaimChanged(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
14 |
15 | struct Claim {
16 | uint256 claimType;
17 | uint256 scheme;
18 | address issuer; // msg.sender
19 | bytes signature; // this.address + claimType + data
20 | bytes data;
21 | string uri;
22 | }
23 |
24 | function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
25 | function getClaimIdsByType(uint256 _claimType) public constant returns(bytes32[] claimIds);
26 | function addClaim(uint256 _claimType, uint256 _scheme, address issuer, bytes _signature, bytes _data, string _uri) public returns (bytes32 claimRequestId);
27 | function removeClaim(bytes32 _claimId) public returns (bool success);
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveKey.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class RemoveKey extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
31 | >
32 |
33 |
34 |
35 |
Are you sure?
36 | {this.props.wrongOwner && (
37 |
38 | {`Active wallet does not own this identity`}
39 |
40 | )}
41 | {this.state.error && (
42 |
Error! Please try again.
43 | )}
44 |
45 | {
48 | this.props.removeKey()
49 | }}
50 | >
51 | Remove Key
52 |
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | export default RemoveKey
61 |
--------------------------------------------------------------------------------
/src/pages/identity/_ValidateClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | fromRpcSig,
4 | ecrecover,
5 | toBuffer,
6 | bufferToHex,
7 | pubToAddress
8 | } from 'ethereumjs-util'
9 |
10 | import ClaimHolder from '../../contracts/ClaimHolder'
11 |
12 | class ValidateClaim extends Component {
13 | constructor() {
14 | super()
15 | this.state = { valid: false, icon: 'fa-spinner fa-spin' }
16 | }
17 |
18 | componentDidMount() {
19 | this.validate()
20 | }
21 |
22 | async validate() {
23 | var claim = this.props.claim,
24 | hasKey
25 |
26 | var hashedSignature = web3.utils.soliditySha3(
27 | this.props.subject,
28 | claim.claimType,
29 | claim.data
30 | )
31 | const prefixedMsg = web3.eth.accounts.hashMessage(hashedSignature)
32 |
33 | if (claim.scheme === '4') {
34 | hasKey = true
35 | } else {
36 | var dataBuf = toBuffer(prefixedMsg)
37 | var sig = fromRpcSig(claim.signature)
38 | var recovered = ecrecover(dataBuf, sig.v, sig.r, sig.s)
39 | var recoveredKeyBuf = pubToAddress(recovered)
40 | var recoveredKey = bufferToHex(recoveredKeyBuf)
41 | var hashedRecovered = web3.utils.soliditySha3(recoveredKey)
42 |
43 | var issuer = new web3.eth.Contract(ClaimHolder.abi, claim.issuer)
44 | try {
45 | hasKey = await issuer.methods.keyHasPurpose(hashedRecovered, 3).call()
46 | } catch (e) {
47 | /* Ignore */
48 | }
49 | }
50 |
51 | this.setState({
52 | icon: hasKey ? 'fa-check' : 'fa-times',
53 | className: hasKey ? 'text-success' : 'text-danger',
54 | text: hasKey ? 'Valid' : 'Invalid'
55 | })
56 | }
57 |
58 | render() {
59 | return (
60 |
61 | {this.state.text}
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | export default ValidateClaim
69 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class RemoveClaim extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
31 | >
32 |
33 |
34 |
35 |
Are you sure?
36 | {this.props.wrongOwner && (
37 |
38 | {`Active wallet does not own this identity`}
39 |
40 | )}
41 | {this.state.error && (
42 |
Error! Please try again.
43 | )}
44 |
45 | {
48 | this.props.removeClaim()
49 | }}
50 | >
51 | Remove Claim
52 |
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | export default RemoveClaim
61 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import serveStatic from 'serve-static'
3 | import { spawn } from 'child_process'
4 | import Ganache from 'ganache-core'
5 | import opener from 'opener'
6 | import fs from 'fs'
7 | import Web3 from 'web3'
8 |
9 | import simpleIssuer from './issuer-services/_simple'
10 |
11 | const HOST = process.env.HOST || 'localhost'
12 | const app = express()
13 |
14 | app.get('/', (req, res) => {
15 | var html = fs.readFileSync(__dirname + '/public/dev.html').toString()
16 | res.send(html.replace(/\{HOST\}/g, `http://${HOST}:8082/`))
17 | })
18 | app.use(serveStatic('public'))
19 |
20 | try {
21 | var { simpleApp } = require('./issuer-services/config.json')
22 | simpleIssuer(app, { web3: new Web3(), simpleApp })
23 | } catch(e) {
24 | /* Ignore */
25 | }
26 |
27 | const startGanache = () =>
28 | new Promise((resolve, reject) => {
29 | try {
30 | fs.mkdirSync('./data/db')
31 | } catch (e) {
32 | /* Ignore */
33 | }
34 | var server = Ganache.server({
35 | total_accounts: 5,
36 | default_balance_ether: 100,
37 | db_path: 'data/db',
38 | network_id: 999,
39 | seed: 123
40 | // blocktime: 3
41 | })
42 | server.listen(8545, err => {
43 | if (err) {
44 | return reject(err)
45 | }
46 | console.log('Ganache listening. Starting webpack...')
47 | resolve()
48 | })
49 | })
50 |
51 | async function start() {
52 | await startGanache()
53 | const webpackDevServer = spawn('./node_modules/.bin/webpack-dev-server', [
54 | '--info=false',
55 | '--port=8082',
56 | '--host=0.0.0.0'
57 | ])
58 | webpackDevServer.stdout.pipe(process.stdout)
59 | webpackDevServer.stderr.pipe(process.stderr)
60 | process.on('exit', () => webpackDevServer.kill())
61 |
62 | const PORT = process.env.PORT || 3000
63 | app.listen(PORT, () => {
64 | console.log(`\nListening on port ${PORT}\n`)
65 | setTimeout(() => {
66 | opener(`http://${HOST}:${PORT}`)
67 | }, 2000)
68 | })
69 | }
70 |
71 | start()
72 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/KeyDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { keyPurpose, keyType } from 'actions/Identity'
4 |
5 | import Identity from 'contracts/ClaimHolder'
6 | import Modal from 'components/Modal'
7 | import Row from 'components/DetailRow'
8 |
9 | class KeyDetail extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | }
14 |
15 | async componentDidMount() {
16 | var identity = new web3.eth.Contract(
17 | Identity.abi,
18 | this.props.identity.address
19 | )
20 | var key = await identity.methods.getKey(this.props.keyId).call()
21 | if (key) {
22 | this.setState({ key })
23 | }
24 | }
25 |
26 | render() {
27 | const { key } = this.state
28 | if (!key) {
29 | return null
30 | }
31 |
32 | return (
33 | this.props.onClose()}
37 | >
38 | Key Detail
39 |
40 |
41 |
45 | {this.props.identity.address}
46 |
47 |
48 | {`${keyPurpose(key.purpose)} (${key.purpose})`}
49 |
50 |
51 | {`${keyType(key.keyType)} (${key.keyType})`}
52 |
53 |
54 | {this.props.keyId}
55 |
56 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | export default KeyDetail
64 |
--------------------------------------------------------------------------------
/src/components/DetailRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class DetailRow extends Component {
4 | constructor() {
5 | super()
6 | this.state = { expanded: false }
7 | }
8 | render() {
9 | var { label, className } = this.props
10 | return (
11 | this.setState({ mouseDown: +new Date() })}
14 | onMouseUp={() => {
15 | if (this.state.mode === 'info') {
16 | this.setState({ mode: null })
17 | } else if (
18 | this.state.expanded &&
19 | +new Date() - this.state.mouseDown < 250
20 | ) {
21 | this.setState({ expanded: false })
22 | } else if (!this.state.expanded) {
23 | this.setState({ expanded: true })
24 | }
25 | }}
26 | >
27 | {label}
28 |
29 | {this.state.mode === 'info' ? (
30 | {this.props.info}
31 | ) : (
32 |
33 | {this.props.children}
34 |
35 | )}
36 |
37 |
38 | {!this.props.info ? null : (
39 | e.preventDefault()}
43 | onMouseUp={e => {
44 | e.stopPropagation()
45 | if (this.state.mode === 'info') {
46 | this.setState({ mode: null, expanded: false })
47 | } else {
48 | this.setState({ mode: 'info', expanded: false })
49 | }
50 | }}
51 | >
52 |
53 |
54 | )}
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | export default DetailRow
62 |
--------------------------------------------------------------------------------
/test/ClaimVerifier.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import helper from './_helper'
3 |
4 | describe('ClaimVerifier.sol', async function() {
5 | var web3, accounts, deploy, prvSigner, pubSigner
6 | var UserIdentity, ClaimIssuer, ClaimVerifier
7 |
8 | before(async function() {
9 | ({ deploy, accounts, web3 } = await helper(
10 | `${__dirname}/../contracts/`
11 | ))
12 |
13 | prvSigner = web3.utils.randomHex(32)
14 | pubSigner = web3.eth.accounts.privateKeyToAccount(prvSigner).address
15 |
16 | UserIdentity = await deploy('ClaimHolder', { from: accounts[0] })
17 | ClaimIssuer = await deploy('ClaimHolder', { from: accounts[1] })
18 | ClaimVerifier = await deploy('ClaimVerifier', { from: accounts[2], args: [
19 | ClaimIssuer._address
20 | ] })
21 | })
22 |
23 | it('should allow verifier owner to addKey', async function() {
24 | var key = web3.utils.sha3(pubSigner)
25 | var result = await ClaimIssuer.methods.addKey(key, 3, 1).send({ from: accounts[1] })
26 |
27 | assert(result)
28 | })
29 |
30 | it('should not allow new listing without identity claim', async function() {
31 | var res = await ClaimVerifier.methods
32 | .checkClaim(UserIdentity._address, 3)
33 | .send({ from: accounts[0] })
34 | assert(res.events.ClaimInvalid)
35 | })
36 |
37 | it('should allow identity owner to addClaim', async function() {
38 | var data = web3.utils.asciiToHex('Verified OK')
39 | var claimType = 3
40 | var hashed = web3.utils.soliditySha3(UserIdentity._address, claimType, data)
41 | var signed = await web3.eth.accounts.sign(hashed, prvSigner)
42 |
43 | var claimRes = await UserIdentity.methods
44 | .addClaim(
45 | claimType,
46 | 2,
47 | ClaimIssuer._address,
48 | signed.signature,
49 | data,
50 | 'abc.com'
51 | ).send({ from: accounts[0] })
52 |
53 | assert(claimRes.events.ClaimAdded)
54 | })
55 |
56 | it('should not allow new listing without identity claim', async function() {
57 | var res = await ClaimVerifier.methods
58 | .checkClaim(UserIdentity._address, 3)
59 | .send({ from: accounts[0] })
60 | assert(res.events.ClaimValid)
61 | })
62 |
63 | })
64 |
--------------------------------------------------------------------------------
/src/pages/console/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { loadWallet } from 'actions/Wallet'
5 |
6 | import Providers from './_Providers'
7 | import Accounts from './_Accounts'
8 |
9 | class Console extends Component {
10 | constructor() {
11 | super()
12 | this.state = {}
13 | }
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | Wallet Manager:
22 |
23 | this.props.loadWallet(false)}
28 | >
29 | In-Browser
30 |
31 | {
36 | if (this.props.browserHasProvider) {
37 | this.props.loadWallet(true)
38 | } else {
39 | alert('Please unlock MetaMask')
40 | }
41 | }}
42 | >
43 | MetaMask
44 |
45 |
46 |
47 | {this.props.externalProvider ? null : (
48 | <>
49 |
50 |
51 | >
52 | )}
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | const mapStateToProps = state => ({
61 | externalProvider: state.wallet.externalProvider,
62 | browserHasProvider: state.network.browserProvider ? true : false
63 | })
64 |
65 | const mapDispatchToProps = dispatch => ({
66 | loadWallet: external => {
67 | dispatch(loadWallet(external))
68 | }
69 | })
70 |
71 | export default connect(mapStateToProps, mapDispatchToProps)(Console)
72 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_CheckClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | class CheckClaim extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | identity: props.identities[0] ? props.identities[0].address : '',
12 | verifier: props.verifier.address,
13 | submitted: false
14 | }
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (this.props.response !== 'success' && nextProps.response === 'success') {
19 | this.setState({ shouldClose: true, submitted: true })
20 | }
21 | if (
22 | this.props.response !== 'submitted' &&
23 | nextProps.response === 'submitted'
24 | ) {
25 | this.setState({ loading: true })
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 | this.props.onClose()}
36 | >
37 |
38 |
39 |
Check for valid claim:
40 |
41 |
42 |
43 | {
47 | this.setState({
48 | identity: e.currentTarget.value
49 | })
50 | }}
51 | >
52 | {(this.props.identities || []).map((identity, idx) => (
53 |
54 | {identity.name}
55 |
56 | ))}
57 |
58 |
59 |
60 |
61 |
62 | {
65 | this.setState({ submitted: true })
66 | this.props.checkClaim(
67 | this.state.verifier,
68 | this.state.identity,
69 | this.props.verifier.claimType
70 | )
71 | }}
72 | >
73 | Check Claim
74 |
75 |
76 |
77 |
78 | )
79 | }
80 | }
81 |
82 | export default CheckClaim
83 |
--------------------------------------------------------------------------------
/contracts/Identity.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ClaimHolder.sol';
4 |
5 | // **Warning!** This file is a protoype version of our work around ERC 725.
6 | // This file is now out of date and **should not be used**.
7 | // Our current identity contracts are here:
8 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
9 |
10 | /**
11 | * NOTE: This contract exists as a convenience for deploying an identity with
12 | * some 'pre-signed' claims. If you don't care about that, just use ClaimHolder
13 | * instead.
14 | */
15 |
16 | contract Identity is ClaimHolder {
17 |
18 | function Identity(
19 | uint256[] _claimType,
20 | uint256[] _scheme,
21 | address[] _issuer,
22 | bytes _signature,
23 | bytes _data,
24 | string _uri,
25 | uint256[] _sigSizes,
26 | uint256[] dataSizes,
27 | uint256[] uriSizes
28 | )
29 | public
30 | {
31 | bytes32 claimId;
32 | uint offset = 0;
33 | uint uoffset = 0;
34 | uint doffset = 0;
35 |
36 | for (uint i = 0; i < _claimType.length; i++) {
37 |
38 | claimId = keccak256(_issuer[i], _claimType[i]);
39 |
40 | claims[claimId] = Claim(
41 | _claimType[i],
42 | _scheme[i],
43 | _issuer[i],
44 | getBytes(_signature, offset, _sigSizes[i]),
45 | getBytes(_data, doffset, dataSizes[i]),
46 | getString(_uri, uoffset, uriSizes[i])
47 | );
48 |
49 | offset += _sigSizes[i];
50 | uoffset += uriSizes[i];
51 | doffset += dataSizes[i];
52 |
53 | emit ClaimAdded(
54 | claimId,
55 | claims[claimId].claimType,
56 | claims[claimId].scheme,
57 | claims[claimId].issuer,
58 | claims[claimId].signature,
59 | claims[claimId].data,
60 | claims[claimId].uri
61 | );
62 | }
63 | }
64 |
65 | function getBytes(bytes _str, uint256 _offset, uint256 _length) constant returns (bytes) {
66 | bytes memory sig = new bytes(_length);
67 | uint256 j = 0;
68 | for (uint256 k = _offset; k< _offset + _length; k++) {
69 | sig[j] = _str[k];
70 | j++;
71 | }
72 | return sig;
73 | }
74 |
75 | function getString(string _str, uint256 _offset, uint256 _length) constant returns (string) {
76 | bytes memory strBytes = bytes(_str);
77 | bytes memory sig = new bytes(_length);
78 | uint256 j = 0;
79 | for (uint256 k = _offset; k< _offset + _length; k++) {
80 | sig[j] = strBytes[k];
81 | j++;
82 | }
83 | return string(sig);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/Dropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | export default class Dropdown extends Component {
4 | constructor(props) {
5 | super(props)
6 | this.state = { open: false }
7 | this.onBlur = this.onBlur.bind(this)
8 | this.onClick = this.onClick.bind(this)
9 | this.onToggle = this.onToggle.bind(this)
10 | }
11 |
12 | componentWillUnmount() {
13 | document.removeEventListener('click', this.onBlur)
14 | }
15 |
16 | render() {
17 | var size = this.props.size,
18 | active = this.state.open ? ' active' : '',
19 | disabled = this.props.disabled ? ' disabled' : ''
20 |
21 | return (
22 |
61 | )
62 | }
63 |
64 | onToggle(e) {
65 | e.preventDefault()
66 | if (this.state.open) {
67 | document.removeEventListener('click', this.onBlur)
68 | } else {
69 | document.addEventListener('click', this.onBlur)
70 | }
71 | this.setState({ open: !this.state.open })
72 | }
73 |
74 | onClick() {
75 | if (this.state.open) {
76 | this.setState({ mouseOver: true, open: false })
77 | document.removeEventListener('click', this.onBlur)
78 | }
79 | }
80 |
81 | onBlur() {
82 | if (!this.state.mouseOver) {
83 | this.setState({ open: false })
84 | }
85 | }
86 | }
87 |
88 | Dropdown.defaultProps = {
89 | size: '',
90 | style: { position: 'relative', display: 'inline-block' }
91 | }
92 |
--------------------------------------------------------------------------------
/contracts/ClaimVerifier.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ClaimHolder.sol';
4 |
5 | // **Warning!** This file is a protoype version of our work around ERC 725.
6 | // This file is now out of date and **should not be used**.
7 | // Our current identity contracts are here:
8 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
9 |
10 | contract ClaimVerifier {
11 |
12 | event ClaimValid(ClaimHolder _identity, uint256 claimType);
13 | event ClaimInvalid(ClaimHolder _identity, uint256 claimType);
14 |
15 | ClaimHolder public trustedClaimHolder;
16 |
17 | function ClaimVerifier(address _trustedClaimHolder) public {
18 | trustedClaimHolder = ClaimHolder(_trustedClaimHolder);
19 | }
20 |
21 | function checkClaim(ClaimHolder _identity, uint256 claimType)
22 | public
23 | returns (bool claimValid)
24 | {
25 | if (claimIsValid(_identity, claimType)) {
26 | emit ClaimValid(_identity, claimType);
27 | return true;
28 | } else {
29 | emit ClaimInvalid(_identity, claimType);
30 | return false;
31 | }
32 | }
33 |
34 | function claimIsValid(ClaimHolder _identity, uint256 claimType)
35 | public
36 | constant
37 | returns (bool claimValid)
38 | {
39 | uint256 foundClaimType;
40 | uint256 scheme;
41 | address issuer;
42 | bytes memory sig;
43 | bytes memory data;
44 |
45 | // Construct claimId (identifier + claim type)
46 | bytes32 claimId = keccak256(trustedClaimHolder, claimType);
47 |
48 | // Fetch claim from user
49 | ( foundClaimType, scheme, issuer, sig, data, ) = _identity.getClaim(claimId);
50 |
51 | bytes32 dataHash = keccak256(_identity, claimType, data);
52 | bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", dataHash);
53 |
54 | // Recover address of data signer
55 | address recovered = getRecoveredAddress(sig, prefixedHash);
56 |
57 | // Take hash of recovered address
58 | bytes32 hashedAddr = keccak256(recovered);
59 |
60 | // Does the trusted identifier have they key which signed the user's claim?
61 | return trustedClaimHolder.keyHasPurpose(hashedAddr, 3);
62 | }
63 |
64 | function getRecoveredAddress(bytes sig, bytes32 dataHash)
65 | public
66 | view
67 | returns (address addr)
68 | {
69 | bytes32 ra;
70 | bytes32 sa;
71 | uint8 va;
72 |
73 | // Check the signature length
74 | if (sig.length != 65) {
75 | return (0);
76 | }
77 |
78 | // Divide the signature in r, s and v variables
79 | assembly {
80 | ra := mload(add(sig, 32))
81 | sa := mload(add(sig, 64))
82 | va := byte(0, mload(add(sig, 96)))
83 | }
84 |
85 | if (va < 27) {
86 | va += 27;
87 | }
88 |
89 | address recoveredAddress = ecrecover(dataHash, va, ra, sa);
90 |
91 | return (recoveredAddress);
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/reducers/Network.js:
--------------------------------------------------------------------------------
1 | import { NetworkConstants } from 'actions/Network'
2 |
3 | import Providers from 'constants/Providers'
4 |
5 | const HOST = process.env.HOST || 'localhost'
6 | let ipfsGateway = 'https://gateway.originprotocol.com',
7 | ipfsRPC = 'https://gateway.originprotocol.com',
8 | provider = 'https://rinkeby.infura.io',
9 | browserProvider = false
10 |
11 | if (process.env.NODE_ENV !== 'production') {
12 | ipfsGateway = `http://${HOST}:9090`
13 | ipfsRPC = `http://${HOST}:5002`
14 | provider = `http://${HOST}:8545`
15 | }
16 |
17 | if (typeof window !== 'undefined') {
18 | provider = window.sessionStorage.provider || provider
19 | if (window.web3) {
20 | browserProvider = web3.currentProvider
21 | }
22 | window.web3 = new Web3(provider)
23 | }
24 |
25 | const initialState = {
26 | id: null,
27 | contract: null,
28 | accounts: [],
29 | account: null,
30 | status: 'disconnected',
31 |
32 | browserProvider,
33 | providers: Providers,
34 | provider,
35 |
36 | ipfsGateway,
37 | ipfsRPC
38 | }
39 |
40 | export default function Network(state = initialState, action = {}) {
41 | switch (action.type) {
42 | case NetworkConstants.GET_CONTRACT_SUCCESS:
43 | return {
44 | ...state,
45 | contract: action.contract
46 | }
47 |
48 | case NetworkConstants.CHANGE_SUCCESS:
49 | return {
50 | ...state,
51 | status: 'connected',
52 | id: action.id,
53 | contract: action.contract,
54 | accounts: action.accounts,
55 | account: action.accounts[0]
56 | }
57 |
58 | case NetworkConstants.CHANGE_ERROR:
59 | return {
60 | ...state,
61 | status: 'error'
62 | }
63 |
64 | case NetworkConstants.SELECT_ACCOUNT:
65 | return {
66 | ...state,
67 | account: state.accounts.find(a => a.hash === action.hash)
68 | }
69 |
70 | case NetworkConstants.UPDATE_BALANCE_SUCCESS:
71 | var acctIdx = state.accounts.findIndex(a => a.hash === action.account)
72 | if (acctIdx < 0) {
73 | return state
74 | }
75 | var accounts = [...state.accounts],
76 | account = {
77 | hash: action.account,
78 | balance: action.balance
79 | }
80 | accounts[acctIdx] = account
81 | return {
82 | ...state,
83 | accounts,
84 | account
85 | }
86 |
87 | case NetworkConstants.SET_PROVIDER:
88 | window.sessionStorage.provider = action.provider
89 | return {
90 | ...state,
91 | status: 'connecting',
92 | provider: action.provider
93 | }
94 |
95 | case NetworkConstants.SET_IPFS:
96 | return {
97 | ...state,
98 | ipfsGateway: action.gateway,
99 | ipfsRPC: action.api
100 | }
101 | }
102 |
103 | return state
104 | }
105 |
--------------------------------------------------------------------------------
/public/vendor/driver.min.css:
--------------------------------------------------------------------------------
1 | div#driver-popover-item{display:none;position:absolute;background:#fff;color:#2d2d2d;margin:0;padding:15px;border-radius:5px;min-width:250px;max-width:300px;box-shadow:0 1px 10px rgba(0,0,0,.4);z-index:1000000000}div#driver-popover-item .driver-popover-tip{border:5px solid #fff;content:"";position:absolute}div#driver-popover-item .driver-popover-tip.bottom{bottom:-10px;border-top-color:#fff;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:#fff;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:#fff}div#driver-popover-item .driver-popover-tip.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:#fff;border-left-color:transparent}div#driver-popover-item .driver-popover-footer{display:block;clear:both;margin-top:5px}div#driver-popover-item .driver-popover-footer button{display:inline-block;padding:3px 10px;border:1px solid #d4d4d4;text-decoration:none;text-shadow:1px 1px 0 #fff;color:#2d2d2d;font:11px/normal sans-serif;cursor:pointer;outline:0;background-color:#f1f1f1;border-radius:2px;zoom:1;margin:10px 0 0;line-height:1.3}div#driver-popover-item .driver-popover-footer button.driver-disabled{color:gray;cursor:default;pointer-events:none}div#driver-popover-item .driver-popover-footer .driver-close-btn{float:left}div#driver-popover-item .driver-popover-footer .driver-btn-group{float:right}div#driver-popover-item .driver-popover-title{font:19px/normal sans-serif;margin:0 0 5px;font-weight:700;display:block;position:relative;line-height:1.5;zoom:1}div#driver-popover-item .driver-popover-description{margin-bottom:0;font:14px/normal sans-serif;line-height:1.5;color:#2d2d2d;font-weight:400;zoom:1}.driver-no-animation{-webkit-transition:none!important;-moz-transition:none!important;-ms-transition:none!important;-o-transition:none!important;transition:none!important}div#driver-page-overlay{background:#000;position:fixed;bottom:0;right:0;display:block;width:100%;height:100%;zoom:1;filter:alpha(opacity=75);opacity:.75;z-index:100002!important}div#driver-highlighted-element-stage,div#driver-page-overlay{top:0;left:0;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;transition:all .4s}div#driver-highlighted-element-stage{position:absolute;height:50px;width:300px;background:#fff;z-index:100003!important;display:none}.driver-highlighted-element{z-index:100004!important}.driver-position-relative{position:relative!important}.driver-fix-stacking{z-index:auto!important;opacity:1!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important;-webkit-filter:none!important;-moz-filter:none!important;-ms-filter:none!important;-o-filter:none!important;filter:none!important;-webkit-perspective:none!important;-moz-perspective:none!important;-ms-perspective:none!important;-o-perspective:none!important;perspective:none!important}
2 | /*# sourceMappingURL=driver.min.css.map*/
--------------------------------------------------------------------------------
/issuer-services/_github.js:
--------------------------------------------------------------------------------
1 | // https://github.com/settings/developers
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 5 // Has GitHub
8 |
9 | module.exports = function facebook(app, { web3, githubApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/github-auth-response`
11 |
12 | var githubOAuth = new OAuth(
13 | githubApp.client_id,
14 | githubApp.secret,
15 | 'https://github.com',
16 | '/login/oauth/authorize',
17 | '/login/oauth/access_token',
18 | null
19 | )
20 |
21 | app.get('/github-auth', (req, res) => {
22 | if (!req.query.target) {
23 | res.send('No target identity contract provided')
24 | return
25 | }
26 | if (!req.query.issuer) {
27 | res.send('No issuer identity contract provided')
28 | return
29 | }
30 |
31 | req.session.targetIdentity = req.query.target
32 | req.session.issuer = req.query.issuer
33 | req.session.state = web3.utils.randomHex(8)
34 |
35 | var authURL = githubOAuth.getAuthorizeUrl({
36 | redirect_uri,
37 | scope: ['user'],
38 | state: req.session.state
39 | })
40 |
41 | res.redirect(authURL)
42 | })
43 |
44 | app.get(
45 | '/github-auth-response',
46 | (req, res, next) => {
47 | githubOAuth.getOAuthAccessToken(
48 | req.query.code,
49 | { redirect_uri },
50 | function(e, access_token, refresh_token, results) {
51 | if (e) {
52 | next(e)
53 | } else if (results.error) {
54 | next(results.error)
55 | } else {
56 | req.access_token = access_token
57 | next()
58 | }
59 | }
60 | )
61 | },
62 | (req, res, next) => {
63 | superagent
64 | .get('https://api.github.com/user')
65 | .set('Authorization', `token ${req.access_token}`)
66 | .accept('json')
67 | .then(response => {
68 | req.githubUser = response.body
69 | next()
70 | })
71 | },
72 | async (req, res) => {
73 | // var data = JSON.stringify({ user_id: req.githubUser.id })
74 | var rawData = 'Verified OK'
75 | var hexData = web3.utils.asciiToHex(rawData)
76 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
77 | req.signedData = await web3.eth.accounts.sign(hashed, githubApp.claimSignerKey)
78 |
79 | res.send(
80 | HTML(`
81 | Successfully signed claim:
82 | Issuer: ${req.session.issuer}
83 | Target: ${req.session.targetIdentity}
84 | Data: ${rawData}
85 | Signature: ${req.signedData.signature}
86 | Hash: ${req.signedData.messageHash}
87 | OK
88 | `)
95 | )
96 | }
97 | )
98 | }
99 |
--------------------------------------------------------------------------------
/contracts/ClaimHolder.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ERC735.sol';
4 | import './KeyHolder.sol';
5 |
6 | // **Warning!** This file is a protoype version of our work around ERC 725.
7 | // This file is now out of date and **should not be used**.
8 | // Our current identity contracts are here:
9 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
10 |
11 | contract ClaimHolder is KeyHolder, ERC735 {
12 |
13 | mapping (bytes32 => Claim) claims;
14 | mapping (uint256 => bytes32[]) claimsByType;
15 |
16 | function addClaim(
17 | uint256 _claimType,
18 | uint256 _scheme,
19 | address _issuer,
20 | bytes _signature,
21 | bytes _data,
22 | string _uri
23 | )
24 | public
25 | returns (bytes32 claimRequestId)
26 | {
27 | bytes32 claimId = keccak256(_issuer, _claimType);
28 |
29 | if (msg.sender != address(this)) {
30 | require(keyHasPurpose(keccak256(msg.sender), 3), "Sender does not have claim signer key");
31 | }
32 |
33 | if (claims[claimId].issuer != _issuer) {
34 | claimsByType[_claimType].push(claimId);
35 | }
36 |
37 | claims[claimId].claimType = _claimType;
38 | claims[claimId].scheme = _scheme;
39 | claims[claimId].issuer = _issuer;
40 | claims[claimId].signature = _signature;
41 | claims[claimId].data = _data;
42 | claims[claimId].uri = _uri;
43 |
44 | emit ClaimAdded(
45 | claimId,
46 | _claimType,
47 | _scheme,
48 | _issuer,
49 | _signature,
50 | _data,
51 | _uri
52 | );
53 |
54 | return claimId;
55 | }
56 |
57 | function removeClaim(bytes32 _claimId) public returns (bool success) {
58 | if (msg.sender != address(this)) {
59 | require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key");
60 | }
61 |
62 | /* uint index; */
63 | /* (index, ) = claimsByType[claims[_claimId].claimType].indexOf(_claimId);
64 | claimsByType[claims[_claimId].claimType].removeByIndex(index); */
65 |
66 | emit ClaimRemoved(
67 | _claimId,
68 | claims[_claimId].claimType,
69 | claims[_claimId].scheme,
70 | claims[_claimId].issuer,
71 | claims[_claimId].signature,
72 | claims[_claimId].data,
73 | claims[_claimId].uri
74 | );
75 |
76 | delete claims[_claimId];
77 | return true;
78 | }
79 |
80 | function getClaim(bytes32 _claimId)
81 | public
82 | constant
83 | returns(
84 | uint256 claimType,
85 | uint256 scheme,
86 | address issuer,
87 | bytes signature,
88 | bytes data,
89 | string uri
90 | )
91 | {
92 | return (
93 | claims[_claimId].claimType,
94 | claims[_claimId].scheme,
95 | claims[_claimId].issuer,
96 | claims[_claimId].signature,
97 | claims[_claimId].data,
98 | claims[_claimId].uri
99 | );
100 | }
101 |
102 | function getClaimIdsByType(uint256 _claimType)
103 | public
104 | constant
105 | returns(bytes32[] claimIds)
106 | {
107 | return claimsByType[_claimType];
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/issuer-services/_google.js:
--------------------------------------------------------------------------------
1 | // https://console.developers.google.com/apis/credentials
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 6 // Has Google
8 |
9 | module.exports = function facebook(app, { web3, googleApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/google-auth-response`
11 |
12 | var googleOAuth = new OAuth(
13 | googleApp.client_id,
14 | googleApp.secret,
15 | 'https://accounts.google.com',
16 | '/o/oauth2/auth',
17 | '/o/oauth2/token'
18 | )
19 |
20 | app.get('/google-auth', (req, res) => {
21 | if (!req.query.target) {
22 | res.send('No target identity contract provided')
23 | return
24 | }
25 | if (!req.query.issuer) {
26 | res.send('No issuer identity contract provided')
27 | return
28 | }
29 |
30 | req.session.targetIdentity = req.query.target
31 | req.session.issuer = req.query.issuer
32 | req.session.state = web3.utils.randomHex(8)
33 |
34 | var authURL = googleOAuth.getAuthorizeUrl({
35 | redirect_uri,
36 | scope: 'https://www.googleapis.com/auth/userinfo.profile',
37 | state: req.session.state,
38 | response_type: 'code'
39 | })
40 |
41 | res.redirect(authURL)
42 | })
43 |
44 | app.get(
45 | '/google-auth-response',
46 | (req, res, next) => {
47 | googleOAuth.getOAuthAccessToken(
48 | req.query.code,
49 | {
50 | redirect_uri,
51 | grant_type: 'authorization_code'
52 | },
53 | function(e, access_token, refresh_token, results) {
54 | if (e) {
55 | next(e)
56 | } else if (results.error) {
57 | next(results.error)
58 | } else {
59 | req.access_token = access_token
60 | next()
61 | }
62 | }
63 | )
64 | },
65 | (req, res, next) => {
66 | superagent
67 | .get('https://www.googleapis.com/oauth2/v1/userinfo')
68 | .query({
69 | alt: 'json',
70 | access_token: req.access_token
71 | })
72 | .then(response => {
73 | req.googleUser = response.body
74 | next()
75 | })
76 | },
77 | async (req, res) => {
78 | // var data = JSON.stringify({ user_id: req.googleUser.id })
79 |
80 | var rawData = 'Verified OK'
81 | var hexData = web3.utils.asciiToHex(rawData)
82 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
83 | req.signedData = await web3.eth.accounts.sign(hashed, googleApp.claimSignerKey)
84 |
85 | res.send(
86 | HTML(`
87 | Successfully signed claim:
88 | Issuer: ${req.session.issuer}
89 | Target: ${req.session.targetIdentity}
90 | Data: ${rawData}
91 | Signature: ${req.signedData.signature}
92 | Hash: ${req.signedData.messageHash}
93 | OK
94 | `)
101 | )
102 | }
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/issuer-services/_linkedin.js:
--------------------------------------------------------------------------------
1 | // https://developer.linkedin.com/docs/oauth2
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 9 // Has LinkedIn
8 |
9 | module.exports = function facebook(app, { web3, linkedInApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/linkedin-auth-response`
11 |
12 | var linkedInOAuth = new OAuth(
13 | linkedInApp.client_id,
14 | linkedInApp.secret,
15 | 'https://www.linkedin.com',
16 | '/oauth/v2/authorization',
17 | '/oauth/v2/accessToken',
18 | null
19 | )
20 |
21 | app.get('/linkedin-auth', (req, res) => {
22 | if (!req.query.target) {
23 | res.send('No target identity contract provided')
24 | return
25 | }
26 | if (!req.query.issuer) {
27 | res.send('No issuer identity contract provided')
28 | return
29 | }
30 |
31 | req.session.targetIdentity = req.query.target
32 | req.session.issuer = req.query.issuer
33 | req.session.state = web3.utils.randomHex(8)
34 |
35 | var authURL = linkedInOAuth.getAuthorizeUrl({
36 | redirect_uri,
37 | scope: ['r_basicprofile', 'r_emailaddress'],
38 | state: req.session.state,
39 | response_type: 'code'
40 | })
41 |
42 | res.redirect(authURL)
43 | })
44 |
45 | app.get(
46 | '/linkedin-auth-response',
47 | (req, res, next) => {
48 | linkedInOAuth.getOAuthAccessToken(
49 | req.query.code,
50 | {
51 | redirect_uri,
52 | grant_type: 'authorization_code'
53 | },
54 | function(e, access_token, refresh_token, results) {
55 | if (e) {
56 | next(e)
57 | } else if (results.error) {
58 | next(results.error)
59 | } else {
60 | req.access_token = access_token
61 | next()
62 | }
63 | }
64 | )
65 | },
66 | (req, res, next) => {
67 | superagent
68 | .get('https://api.linkedin.com/v1/people/~')
69 | .set('Authorization', `Bearer ${req.access_token}`)
70 | .query({ format: 'json' })
71 | .then(response => {
72 | req.linkedInUser = response.body
73 | next()
74 | })
75 | },
76 | async (req, res) => {
77 | // var data = JSON.stringify({ user_id: req.githubUser.id })
78 | var rawData = 'Verified OK'
79 | var hexData = web3.utils.asciiToHex(rawData)
80 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
81 | req.signedData = await web3.eth.accounts.sign(
82 | hashed,
83 | linkedInApp.claimSignerKey
84 | )
85 |
86 | res.send(
87 | HTML(`
88 | Successfully signed claim:
89 | Issuer: ${req.session.issuer}
90 | Target: ${req.session.targetIdentity}
91 | Data: ${rawData}
92 | Signature: ${req.signedData.signature}
93 | Hash: ${req.signedData.messageHash}
94 | OK
95 | `)
102 | )
103 | }
104 | )
105 | }
106 |
--------------------------------------------------------------------------------
/issuer-services/_twitter.js:
--------------------------------------------------------------------------------
1 | // https://apps.twitter.com/
2 |
3 | var OAuth = require('oauth').OAuth
4 | var HTML = require('./html')
5 |
6 | const ClaimType = 4 // Has Twitter
7 |
8 | module.exports = function facebook(app, { web3, twitterApp, baseUrl }) {
9 | var twitterOAuth = new OAuth(
10 | 'https://api.twitter.com/oauth/request_token',
11 | 'https://api.twitter.com/oauth/access_token',
12 | twitterApp.client_id,
13 | twitterApp.secret,
14 | '1.0',
15 | `${baseUrl}/twitter-auth-response`,
16 | 'HMAC-SHA1'
17 | )
18 |
19 | app.get('/twitter-auth', (req, res) => {
20 | if (!req.query.target) {
21 | res.send('No target identity contract provided')
22 | return
23 | }
24 | if (!req.query.issuer) {
25 | res.send('No issuer identity contract provided')
26 | return
27 | }
28 |
29 | req.session.targetIdentity = req.query.target
30 | req.session.issuer = req.query.issuer
31 |
32 | twitterOAuth.getOAuthRequestToken(function(
33 | error,
34 | oAuthToken,
35 | oAuthTokenSecret
36 | ) {
37 | req.session.oAuthTokenSecret = oAuthTokenSecret
38 | res.redirect(
39 | `https://twitter.com/oauth/authenticate?oauth_token=${oAuthToken}`
40 | )
41 | })
42 | })
43 |
44 | app.get(
45 | '/twitter-auth-response',
46 | (req, res, next) => {
47 | twitterOAuth.getOAuthAccessToken(
48 | req.query.oauth_token,
49 | req.session.oAuthTokenSecret,
50 | req.query.oauth_verifier,
51 | function(error, oAuthAccessToken, oAuthAccessTokenSecret) {
52 | if (error) {
53 | console.log(error)
54 | res.send('Error')
55 | return
56 | }
57 |
58 | req.oAuthAccessToken = oAuthAccessToken
59 | req.oAuthAccessTokenSecret = oAuthAccessTokenSecret
60 |
61 | next()
62 | }
63 | )
64 | },
65 | (req, res, next) => {
66 | twitterOAuth.get(
67 | 'https://api.twitter.com/1.1/account/verify_credentials.json',
68 | req.oAuthAccessToken,
69 | req.oAuthAccessTokenSecret,
70 | function(error, twitterResponseData) {
71 | if (error) {
72 | res.send('Error')
73 | return
74 | }
75 | try {
76 | req.twitterUser = JSON.parse(twitterResponseData)
77 | next()
78 | } catch (parseError) {
79 | res.send("Error parsing response")
80 | }
81 | }
82 | )
83 | },
84 | async (req, res) => {
85 | // var data = JSON.stringify({ user_id: req.twitterUser.id })
86 |
87 | var rawData = 'Verified OK'
88 | var hexData = web3.utils.asciiToHex(rawData)
89 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
90 | req.signedData = await web3.eth.accounts.sign(hashed, twitterApp.claimSignerKey)
91 |
92 | res.send(
93 | HTML(`
94 | Successfully signed claim:
95 | Issuer: ${req.session.issuer}
96 | Target: ${req.session.targetIdentity}
97 | Data: ${rawData}
98 | Signature: ${req.signedData.signature}
99 | Hash: ${req.signedData.messageHash}
100 | OK
101 | `)
108 | )
109 | }
110 | )
111 | }
112 |
--------------------------------------------------------------------------------
/src/reducers/Wallet.js:
--------------------------------------------------------------------------------
1 | import { WalletConstants } from 'actions/Wallet'
2 | import { NetworkConstants } from 'actions/Network'
3 |
4 | import balance from 'utils/balance'
5 |
6 | const initialState = {
7 | externalProvider: false,
8 |
9 | activeAddress: null,
10 | balances: {},
11 | currency: 'eth',
12 | currencyStr: '$',
13 | exchangeRates: {
14 | usd: 400
15 | },
16 |
17 | unsaved: false,
18 | loaded: false,
19 | raw: {},
20 | accounts: [],
21 | active: null,
22 | locked: false,
23 | tryUnlock: false
24 | }
25 |
26 | export default function Wallet(state = initialState, action = {}) {
27 | switch (action.type) {
28 | case WalletConstants.SELECT_ACCOUNT_SUCCESS:
29 | return {
30 | ...state,
31 | activeAddress: action.activeAddress
32 | }
33 |
34 | case WalletConstants.LOAD_EXTERNAL_SUCCESS:
35 | return {
36 | ...state,
37 | activeAddress: action.activeAddress,
38 | accounts: [],
39 | balances: action.balances,
40 | active: null,
41 | loaded: false
42 | }
43 |
44 | case WalletConstants.LOAD:
45 | return { ...state, externalProvider: action.external }
46 |
47 | case WalletConstants.LOAD_SUCCESS:
48 | return {
49 | ...state,
50 | raw: action.wallet,
51 | accounts: action.accounts,
52 | balances: action.balances,
53 | active: action.wallet[0] ? action.wallet[0] : null,
54 | activeAddress: action.wallet[0] ? action.wallet[0].address : null,
55 | loaded: true
56 | }
57 |
58 | case WalletConstants.ADD_ACCOUNT_SUCCESS:
59 | return {
60 | ...state,
61 | raw: action.wallet,
62 | accounts: [...state.accounts, action.account.address],
63 | balances: {
64 | ...state.balances,
65 | [action.account.address]: balance('0', state.exchangeRates)
66 | },
67 | unsaved: true
68 | }
69 |
70 | case WalletConstants.IMPORT_ACCOUNT_SUCCESS:
71 | return {
72 | ...state,
73 | raw: action.wallet,
74 | accounts: [...state.accounts, action.account.address],
75 | balances: {
76 | ...state.balances,
77 | [action.account.address]: action.balance
78 | },
79 | unsaved: true
80 | }
81 |
82 | case WalletConstants.REMOVE_ACCOUNT_SUCCESS:
83 | return {
84 | ...state,
85 | raw: action.wallet,
86 | accounts: state.accounts.filter(h => h !== action.hash),
87 | unsaved: true
88 | }
89 |
90 | case WalletConstants.SAVE_SUCCESS:
91 | return { ...state, unsaved: false }
92 |
93 | case WalletConstants.SET_CURRENCY:
94 | return {
95 | ...state,
96 | currency: action.currency,
97 | currencyStr: action.currency === 'usd' ? '$' : 'ETH'
98 | }
99 |
100 | case WalletConstants.UPDATE_BALANCE_SUCCESS:
101 | return {
102 | ...state,
103 | balances: {
104 | ...state.balances,
105 | [action.account]: action.balance
106 | }
107 | }
108 |
109 | case NetworkConstants.UPDATE_BALANCE_SUCCESS:
110 | return {
111 | ...state,
112 | balances: {
113 | ...state.balances,
114 | [action.account]: action.balance
115 | }
116 | }
117 |
118 | case WalletConstants.LOCK_WALLET:
119 | return {
120 | ...state,
121 | locked: true,
122 | tryUnlock: false
123 | }
124 |
125 | case WalletConstants.UNLOCK_WALLET:
126 | return {
127 | ...state,
128 | tryUnlock: true
129 | }
130 |
131 | case WalletConstants.UNLOCKED_WALLET:
132 | return {
133 | ...state,
134 | tryUnlock: false,
135 | locked: false
136 | }
137 | }
138 |
139 | return state
140 | }
141 |
--------------------------------------------------------------------------------
/issuer-services/_facebook.js:
--------------------------------------------------------------------------------
1 | // https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
2 |
3 | var superagent = require('superagent')
4 | var HTML = require('./html')
5 |
6 | const ClaimType = 3 // Has Facebook
7 |
8 | module.exports = function facebook(app, { web3, facebookApp, baseUrl }) {
9 | const redirect_uri = `${baseUrl}/fb-auth-response`
10 | app.get('/fb-auth', (req, res) => {
11 | if (!req.query.target) {
12 | res.send('No target identity contract provided')
13 | return
14 | }
15 | if (!req.query.issuer) {
16 | res.send('No issuer identity contract provided')
17 | return
18 | }
19 |
20 | req.session.targetIdentity = req.query.target
21 | req.session.issuer = req.query.issuer
22 | req.session.state = web3.utils.randomHex(8)
23 |
24 | var query = [
25 | `client_id=${facebookApp.client_id}`,
26 | `redirect_uri=${redirect_uri}`,
27 | `state=${req.session.state}`
28 | ]
29 | res.redirect(
30 | `https://www.facebook.com/v2.12/dialog/oauth?${query.join('&')}`
31 | )
32 | })
33 |
34 | app.get(
35 | '/fb-auth-response',
36 | (req, res, next) => {
37 | if (!req.query.code) {
38 | return res.send('No Code specified')
39 | }
40 | if (req.query.state !== req.session.state) {
41 | return res.send('State param does not match')
42 | }
43 | if (!req.session.targetIdentity) {
44 | return res.send('No target identity found')
45 | }
46 | if (!req.session.issuer) {
47 | return res.send('No issuer found')
48 | }
49 |
50 | superagent
51 | .get(`https://graph.facebook.com/v2.12/oauth/access_token`)
52 | .query({
53 | client_id: facebookApp.client_id,
54 | client_secret: facebookApp.secret,
55 | redirect_uri,
56 | code: req.query.code
57 | })
58 | .then(response => {
59 | req.userToken = response.body
60 | next()
61 | })
62 | .catch(() => {
63 | res.send('Error fetching token')
64 | })
65 | },
66 | (req, res, next) => {
67 | superagent
68 | .get(`https://graph.facebook.com/debug_token`)
69 | .query({
70 | input_token: req.userToken.access_token,
71 | access_token: `${facebookApp.client_id}|${facebookApp.secret}`
72 | })
73 | .then(response => {
74 | req.tokenDebug = JSON.parse(response.text).data
75 |
76 | if (req.tokenDebug.app_id !== facebookApp.client_id) {
77 | return res.send("Token's App does not match")
78 | }
79 | if (!req.tokenDebug.is_valid) {
80 | return res.send('Token is invalid')
81 | }
82 | next()
83 | })
84 | .catch((e) => {
85 | console.log(e)
86 | res.send('Error validating token')
87 | })
88 | },
89 | async (req, res) => {
90 | // var data = JSON.stringify({ user_id: req.tokenDebug.user_id })
91 | var rawData = 'Verified OK'
92 | var hexData = web3.utils.asciiToHex(rawData)
93 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
94 | req.signedData = await web3.eth.accounts.sign(hashed, facebookApp.claimSignerKey)
95 |
96 | res.send(HTML(`
97 | Successfully signed claim:
98 | Issuer: ${req.session.issuer}
99 | Target: ${req.session.targetIdentity}
100 | Data: ${rawData}
101 | Signature: ${req.signedData.signature}
102 | Hash: ${req.signedData.messageHash}
103 | OK
104 | `
111 | ))
112 | }
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_NewVerifier.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import FormRow from 'components/FormRow'
5 | import Loading from 'components/Loading'
6 |
7 | import { ClaimTypes } from 'actions/Identity'
8 |
9 | class NewVerifier extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | name: '',
14 | claimType: '10',
15 | trustedIdentity: props.identities[0] ? props.identities[0].address : '',
16 | methodName: ''
17 | }
18 | }
19 |
20 | componentWillReceiveProps(nextProps) {
21 | if (this.props.response !== 'success' && nextProps.response === 'success') {
22 | this.setState({ shouldClose: true, submitted: true, loading: false })
23 | }
24 | if (!this.props.response && nextProps.response === 'submitted') {
25 | this.setState({ loading: true })
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 | this.props.onClose()}
37 | onOpen={() => this.nameInput.focus()}
38 | onPressEnter={() => this.onDeploy()}
39 | >
40 |
41 |
42 | Deploy a new Claim Checker Contract:
43 |
44 | {!this.props.identities.length && (
45 |
46 | {`Try deploying a Certifier first`}
47 |
48 | )}
49 |
50 |
107 |
108 | this.onDeploy()}>
109 | Deploy
110 |
111 |
112 |
113 | )
114 | }
115 |
116 | onDeploy() {
117 | const { name, trustedIdentity, methodName, claimType } = this.state
118 | this.props.deployClaimVerifier({
119 | name,
120 | trustedIdentity,
121 | methodName,
122 | claimType
123 | })
124 | }
125 | }
126 |
127 | export default NewVerifier
128 |
--------------------------------------------------------------------------------
/src/components/AccountChooser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Dropdown from './Dropdown'
4 | import Modal from './Modal'
5 |
6 | export default class AccountChooser extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | render() {
13 | const { wallet, balance, account } = this.props
14 | const currency = `${wallet.currency}Str`
15 | return (
16 |
17 |
22 | {`Wallet ${account.substr(0, 6)}: ${balance[currency]}`}
23 |
24 | >
25 | }
26 | >
27 | {wallet.accounts.map((a, idx) => (
28 | {
35 | e.preventDefault()
36 | this.props.selectAccount(a)
37 | }}
38 | >
39 | {`${this.props.wallet.balances[a][currency]}`}
40 | {`${a.substr(0, 8)}`}
41 |
42 | ))}
43 |
44 |
45 |
46 | Network / Wallet Settings
47 |
48 |
49 |
50 |
51 | Prices in:
52 | {
57 | e.stopPropagation()
58 | this.props.setCurrency('usd')
59 | }}
60 | children="USD"
61 | />
62 | {
67 | e.stopPropagation()
68 | this.props.setCurrency('eth')
69 | }}
70 | children="ETH"
71 | />
72 |
73 |
74 |
75 |
76 | {this.props.wallet.tryUnlock && (
77 |
{
80 | this.props.lockWallet()
81 | this.setState({ unlock: false, correct: false })
82 | }}
83 | onOpen={() => {
84 | this.pw.focus()
85 | this.setState({ correct: false })
86 | }}
87 | shouldClose={this.state.unlock}
88 | submitted={this.state.unlock}
89 | className="p-3"
90 | >
91 | {this.state.correct ? (
92 |
93 |
97 |
98 | ) : (
99 |
100 |
(this.pw = r)}
105 | onKeyDown={e => {
106 | if (e.keyCode === 13) {
107 | this.setState({ correct: true })
108 | setTimeout(() => {
109 | this.setState({ unlock: true })
110 | }, 500)
111 | }
112 | }}
113 | />
114 |
115 |
116 |
117 |
118 |
119 |
120 | )}
121 |
122 | )}
123 |
124 | )
125 | }
126 | }
127 |
128 | require('react-styl')(`
129 | .wallet-unlocked
130 | color: green
131 | `)
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Nick Poulden",
3 | "name": "identity-playground",
4 | "version": "0.1.0",
5 | "license": "MIT",
6 | "description": "Working implementation and demonstration of ERC 725",
7 | "main": "index.js",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/OriginProtocol/identity-playground.git"
11 | },
12 | "scripts": {
13 | "test": "mocha -r @babel/register test -t 10000",
14 | "test:watch": "mocha -r @babel/register -w --watch-extensions sol -t 5000 test",
15 | "start": "node -r @babel/register index",
16 | "build:contracts": "BUILD=1 mocha -r @babel/register -t 10000 test",
17 | "build:js": "webpack --config webpack.prod.js --progress",
18 | "build:css": "node -r @babel/register -r ./src/pages/App -e \"console.log(require('react-styl').getCss())\" > public/css/app.css",
19 | "build": "npm run build:js && npm run build:css",
20 | "lint": "eslint .",
21 | "prettier": "find ./src -iname '*.js' | xargs ./node_modules/.bin/prettier --write",
22 | "clean": "rm -rf data/db"
23 | },
24 | "dependencies": {
25 | "@babel/core": "^7.0.0",
26 | "@babel/preset-react": "^7.0.0",
27 | "@babel/register": "^7.0.0",
28 | "ajv": "^6.5.3",
29 | "babel-plugin-module-resolver": "^3.1.1",
30 | "body-parser": "^1.18.3",
31 | "bs58": "^4.0.1",
32 | "date-fns": "^1.29.0",
33 | "express": "^4.16.3",
34 | "express-session": "^1.15.6",
35 | "ganache-core": "2.2.1",
36 | "ipfs": "^0.31.7",
37 | "ipfs-api": "^24.0.1",
38 | "oauth": "^0.9.15",
39 | "prettier": "^1.14.2",
40 | "react": "^16.4.2",
41 | "react-dom": "^16.4.2",
42 | "react-redux": "^5.0.7",
43 | "react-styl": "^0.0.3",
44 | "redux": "^4.0.0",
45 | "redux-logger": "^3.0.6",
46 | "redux-thunk": "^2.3.0",
47 | "serve-static": "^1.13.2",
48 | "solc": "^0.4.24",
49 | "superagent": "^3.8.3",
50 | "web3": "^1.0.0-beta.36"
51 | },
52 | "prettier": {
53 | "semi": false,
54 | "singleQuote": true,
55 | "bracketSpacing": true
56 | },
57 | "babel": {
58 | "presets": [
59 | "@babel/preset-env",
60 | "@babel/preset-react"
61 | ],
62 | "plugins": [
63 | [
64 | "module-resolver",
65 | {
66 | "alias": {
67 | "actions": "./src/actions",
68 | "components": "./src/components",
69 | "constants": "./src/constants",
70 | "contracts": "./src/contracts",
71 | "pages": "./src/pages",
72 | "reducers": "./src/reducers",
73 | "utils": "./src/utils"
74 | }
75 | }
76 | ],
77 | "@babel/plugin-transform-runtime",
78 | "@babel/plugin-transform-destructuring",
79 | "@babel/plugin-transform-object-assign",
80 | "@babel/plugin-proposal-object-rest-spread"
81 | ]
82 | },
83 | "devDependencies": {
84 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
85 | "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
86 | "@babel/plugin-transform-destructuring": "^7.0.0",
87 | "@babel/plugin-transform-object-assign": "^7.0.0",
88 | "@babel/plugin-transform-runtime": "^7.0.0",
89 | "@babel/preset-env": "^7.0.0",
90 | "@babel/runtime": "^7.0.0",
91 | "babel-eslint": "^9.0.0",
92 | "babel-loader": "^8.0.2",
93 | "clean-webpack-plugin": "^0.1.19",
94 | "eslint": "^5.5.0",
95 | "eslint-plugin-babel": "^5.1.0",
96 | "eslint-plugin-react": "^7.11.1",
97 | "opener": "^1.5.1",
98 | "react-router": "^4.3.1",
99 | "react-router-dom": "^4.3.1",
100 | "webpack": "^4.17.2",
101 | "webpack-cli": "^3.1.0",
102 | "webpack-dev-middleware": "^3.2.0",
103 | "webpack-dev-server": "^v3.1.7"
104 | },
105 | "eslintConfig": {
106 | "parser": "babel-eslint",
107 | "parserOptions": {
108 | "ecmaVersion": 6,
109 | "sourceType": "module",
110 | "ecmaFeatures": {
111 | "jsx": true,
112 | "impliedStrict": true
113 | }
114 | },
115 | "globals": {
116 | "Web3": true,
117 | "web3": true,
118 | "OfficialIdentities": true
119 | },
120 | "env": {
121 | "browser": true,
122 | "node": true,
123 | "es6": true,
124 | "mocha": true
125 | },
126 | "plugins": [
127 | "react"
128 | ],
129 | "extends": [
130 | "eslint:recommended",
131 | "plugin:react/recommended"
132 | ],
133 | "rules": {
134 | "react/prop-types": "off",
135 | "react/no-children-prop": "off",
136 | "no-console": "off"
137 | }
138 | },
139 | "eslintIgnore": [
140 | "node_modules",
141 | "public"
142 | ]
143 | }
144 |
--------------------------------------------------------------------------------
/src/pages/console/_Providers.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import {
5 | fetchAccounts,
6 | init,
7 | setProvider,
8 | sendFromNode
9 | } from 'actions/Network'
10 |
11 | class Console extends Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {}
15 | }
16 |
17 | render() {
18 | var activeProvider =
19 | this.props.providers.find(
20 | p => (p.endpoints || []).indexOf(this.props.provider) >= 0
21 | ) || this.props.providers[this.props.providers.length - 1]
22 |
23 | return (
24 |
25 |
26 |
27 | {this.props.providers.map((p, idx) => (
28 | this.setProvider(idx)}
34 | >
35 | {p.name}
36 |
37 | ))}
38 |
39 |
40 | {(activeProvider.endpoints || []).map(e => (
41 | this.props.setProvider(e)}
47 | >
48 | {e.split(':')[0].toUpperCase()}
49 |
50 | ))}
51 |
52 |
53 |
54 | {this.renderStatus()}
55 | {`to ${this.props.provider}`}
56 |
57 |
58 | {/*
59 |
Net ID: {this.props.netId}
60 |
61 | {this.props.accounts.length > 0 &&
62 |
63 |
{`~989 ETH in ${
64 | this.props.accounts.length
65 | } node managed accounts`}
66 |
67 | Transfer
68 | {'From: '}
69 | this.from = ref}>
70 | {this.props.accounts.map(a =>
71 |
72 | {`${a.hash.substr(0, 8)} - ${a.balance.eth} ETH`}
73 |
74 | )}
75 |
76 |
77 | To:
78 | this.to = ref}>
79 | {this.props.wallet.accounts.map((a, idx) =>
80 |
81 | {`${a.substr(0, 8)} - ${this.props.wallet.balances[a].eth} ETH`}
82 |
83 | )}
84 |
85 |
86 | Amount:
87 | this.amount = ref} type="text" defaultValue="5" />
88 |
89 | {
90 | this.props.sendFromNode(this.from.value, this.to.value, this.amount.value)
91 | }}>Go
92 |
93 |
94 | } */}
95 |
96 | )
97 | }
98 |
99 | setProvider(idx) {
100 | var endpoints = this.props.providers[idx].endpoints || []
101 | var endpoint =
102 | endpoints.find(
103 | e => e.split(':')[0] === this.props.provider.split(':')[0]
104 | ) || endpoints[0]
105 |
106 | if (endpoint) {
107 | this.setState({ customEndpoint: false })
108 | this.props.setProvider(endpoint)
109 | } else {
110 | this.setState({ customEndpoint: true })
111 | }
112 | }
113 |
114 | renderStatus() {
115 | var color = 'green',
116 | text = 'Connected'
117 | if (this.props.status === 'disconnected') {
118 | text = 'Disconnected'
119 | color = 'red'
120 | } else if (this.props.status === 'connecting') {
121 | text = 'Connecting'
122 | color = 'orange'
123 | } else if (this.props.status === 'error') {
124 | text = 'Error'
125 | color = 'red'
126 | }
127 | return (
128 |
129 | {text}
130 |
131 | )
132 | }
133 | }
134 |
135 | const mapStateToProps = state => ({
136 | accounts: state.network.accounts,
137 | account: state.network.account,
138 | providers: state.network.providers,
139 | provider: state.network.provider,
140 | netId: state.network.id,
141 | status: state.network.status,
142 | wallet: state.wallet
143 | })
144 |
145 | const mapDispatchToProps = dispatch => ({
146 | init: () => dispatch(init()),
147 | fetchAccounts: () => dispatch(fetchAccounts()),
148 | setProvider: provider => dispatch(setProvider(provider)),
149 | sendFromNode: (from, to, value) => dispatch(sendFromNode(from, to, value))
150 | })
151 |
152 | export default connect(mapStateToProps, mapDispatchToProps)(Console)
153 |
--------------------------------------------------------------------------------
/contracts/KeyHolder.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ERC725.sol';
4 |
5 | // **Warning!** This file is a protoype version of our work around ERC 725.
6 | // This file is now out of date and **should not be used**.
7 | // Our current identity contracts are here:
8 | // https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity
9 |
10 | contract KeyHolder is ERC725 {
11 |
12 | uint256 executionNonce;
13 |
14 | struct Execution {
15 | address to;
16 | uint256 value;
17 | bytes data;
18 | bool approved;
19 | bool executed;
20 | }
21 |
22 | mapping (bytes32 => Key) keys;
23 | mapping (uint256 => bytes32[]) keysByPurpose;
24 | mapping (uint256 => Execution) executions;
25 |
26 | event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
27 |
28 | function KeyHolder() public {
29 | bytes32 _key = keccak256(msg.sender);
30 | keys[_key].key = _key;
31 | keys[_key].purpose = 1;
32 | keys[_key].keyType = 1;
33 | keysByPurpose[1].push(_key);
34 | emit KeyAdded(_key, keys[_key].purpose, 1);
35 | }
36 |
37 | function getKey(bytes32 _key)
38 | public
39 | view
40 | returns(uint256 purpose, uint256 keyType, bytes32 key)
41 | {
42 | return (keys[_key].purpose, keys[_key].keyType, keys[_key].key);
43 | }
44 |
45 | function getKeyPurpose(bytes32 _key)
46 | public
47 | view
48 | returns(uint256 purpose)
49 | {
50 | return (keys[_key].purpose);
51 | }
52 |
53 | function getKeysByPurpose(uint256 _purpose)
54 | public
55 | view
56 | returns(bytes32[] _keys)
57 | {
58 | return keysByPurpose[_purpose];
59 | }
60 |
61 | function addKey(bytes32 _key, uint256 _purpose, uint256 _type)
62 | public
63 | returns (bool success)
64 | {
65 | require(keys[_key].key != _key, "Key already exists"); // Key should not already exist
66 | if (msg.sender != address(this)) {
67 | require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key"); // Sender has MANAGEMENT_KEY
68 | }
69 |
70 | keys[_key].key = _key;
71 | keys[_key].purpose = _purpose;
72 | keys[_key].keyType = _type;
73 |
74 | keysByPurpose[_purpose].push(_key);
75 |
76 | emit KeyAdded(_key, _purpose, _type);
77 |
78 | return true;
79 | }
80 |
81 | function approve(uint256 _id, bool _approve)
82 | public
83 | returns (bool success)
84 | {
85 | require(keyHasPurpose(keccak256(msg.sender), 2), "Sender does not have action key");
86 |
87 | emit Approved(_id, _approve);
88 |
89 | if (_approve == true) {
90 | executions[_id].approved = true;
91 | success = executions[_id].to.call(executions[_id].data, 0);
92 | if (success) {
93 | executions[_id].executed = true;
94 | emit Executed(
95 | _id,
96 | executions[_id].to,
97 | executions[_id].value,
98 | executions[_id].data
99 | );
100 | return;
101 | } else {
102 | emit ExecutionFailed(
103 | _id,
104 | executions[_id].to,
105 | executions[_id].value,
106 | executions[_id].data
107 | );
108 | return;
109 | }
110 | } else {
111 | executions[_id].approved = false;
112 | }
113 | return true;
114 | }
115 |
116 | function execute(address _to, uint256 _value, bytes _data)
117 | public
118 | returns (uint256 executionId)
119 | {
120 | require(!executions[executionNonce].executed, "Already executed");
121 | executions[executionNonce].to = _to;
122 | executions[executionNonce].value = _value;
123 | executions[executionNonce].data = _data;
124 |
125 | emit ExecutionRequested(executionNonce, _to, _value, _data);
126 |
127 | if (keyHasPurpose(keccak256(msg.sender),1) || keyHasPurpose(keccak256(msg.sender),2)) {
128 | approve(executionNonce, true);
129 | }
130 |
131 | executionNonce++;
132 | return executionNonce-1;
133 | }
134 |
135 | function removeKey(bytes32 _key)
136 | public
137 | returns (bool success)
138 | {
139 | require(keys[_key].key == _key, "No such key");
140 | emit KeyRemoved(keys[_key].key, keys[_key].purpose, keys[_key].keyType);
141 |
142 | /* uint index;
143 | (index,) = keysByPurpose[keys[_key].purpose.indexOf(_key);
144 | keysByPurpose[keys[_key].purpose.removeByIndex(index); */
145 |
146 | delete keys[_key];
147 |
148 | return true;
149 | }
150 |
151 | function keyHasPurpose(bytes32 _key, uint256 _purpose)
152 | public
153 | view
154 | returns(bool result)
155 | {
156 | bool isThere;
157 | if (keys[_key].key == 0) return false;
158 | isThere = keys[_key].purpose <= _purpose;
159 | return isThere;
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/test/_helper.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import solc from 'solc'
3 | import linker from 'solc/linker'
4 | import Ganache from 'ganache-core'
5 | import Web3 from 'web3'
6 |
7 | var solcOpts = {
8 | language: 'Solidity',
9 | settings: {
10 | metadata: { useLiteralContent: true },
11 | outputSelection: {
12 | '*': {
13 | '*': ['abi', 'evm.bytecode.object']
14 | }
15 | }
16 | }
17 | }
18 |
19 | // Instantiate a web3 instance. Start a node if one is not already running.
20 | export async function web3Helper(provider = 'ws://localhost:7545') {
21 | var web3 = new Web3(provider)
22 | var instance = await server(web3, provider)
23 | return { web3, server: instance }
24 | }
25 |
26 | function findImportsPath(prefix) {
27 | return function findImports(path) {
28 | try {
29 | return {
30 | contents: fs.readFileSync(prefix + path).toString()
31 | }
32 | } catch (e) {
33 | return { error: 'File not found' }
34 | }
35 | }
36 | }
37 |
38 | export default async function testHelper(contracts, provider) {
39 | const { web3, server } = await web3Helper(provider)
40 | const accounts = await web3.eth.getAccounts()
41 |
42 | async function deploy(contractName, { from, args, log }) {
43 | var sources = {
44 | [contractName]: {
45 | content: fs.readFileSync(`${contracts}/${contractName}.sol`).toString()
46 | }
47 | }
48 | var compileOpts = JSON.stringify({ ...solcOpts, sources })
49 |
50 | // Compile the contract using solc
51 | var rawOutput = solc.compileStandardWrapper(
52 | compileOpts,
53 | findImportsPath(contracts)
54 | )
55 | var output = JSON.parse(rawOutput)
56 |
57 | // If there were any compilation errors, throw them
58 | if (output.errors) {
59 | output.errors.forEach(err => {
60 | if (!err.formattedMessage.match(/Warning:/)) {
61 | throw new SyntaxError(err.formattedMessage)
62 | }
63 | })
64 | }
65 |
66 | var { abi, evm: { bytecode } } = output.contracts[contractName][
67 | contractName
68 | ]
69 |
70 | // Deploy linked libraries
71 | for (let linkedFile in bytecode.linkReferences) {
72 | for (let linkedLib in bytecode.linkReferences[linkedFile]) {
73 | let libObj = output.contracts[linkedFile][linkedLib]
74 | let LibContract = new web3.eth.Contract(libObj.abi)
75 | var libContract = await LibContract.deploy({
76 | data: libObj.evm.bytecode.object
77 | }).send({
78 | from,
79 | gas: 3000000
80 | })
81 |
82 | let libs = { [`${linkedFile}:${linkedLib}`]: libContract._address }
83 |
84 | bytecode.object = linker.linkBytecode(bytecode.object, libs)
85 | }
86 | }
87 |
88 | if (!bytecode.object) {
89 | throw new Error(
90 | 'No Bytecode. Do the method signatures match the interface?'
91 | )
92 | }
93 |
94 | if (process.env.BUILD) {
95 | fs.writeFileSync(
96 | __dirname + '/../src/contracts/' + contractName + '.js',
97 | 'module.exports = ' +
98 | JSON.stringify(
99 | {
100 | abi,
101 | data: bytecode.object
102 | },
103 | null,
104 | 4
105 | )
106 | )
107 | }
108 |
109 | // Instantiate the web3 contract using the abi and bytecode output from solc
110 | var Contract = new web3.eth.Contract(abi)
111 | var contract
112 |
113 | await new Promise(async resolve => {
114 | var chainId = web3.eth.net.getId()
115 |
116 | var data = await Contract.deploy({
117 | data: '0x' + bytecode.object,
118 | arguments: args
119 | }).encodeABI()
120 |
121 | web3.eth
122 | .sendTransaction({
123 | data,
124 | from,
125 | value: 0,
126 | gas: 4612388,
127 | chainId
128 | })
129 | .once('transactionHash', hash => {
130 | if (log) {
131 | console.log('Transaction Hash', hash)
132 | }
133 | })
134 | .once('receipt', receipt => {
135 | if (log) {
136 | console.log(
137 | `Deployed ${contractName} to ${receipt.contractAddress} (${
138 | receipt.cumulativeGasUsed
139 | } gas used)`
140 | )
141 | }
142 | })
143 | .catch('error', err => {
144 | console.log(err)
145 | resolve()
146 | })
147 | .then(instance => {
148 | contract = new web3.eth.Contract(abi, instance.contractAddress)
149 | resolve()
150 | })
151 | })
152 |
153 | if (contract) {
154 | // Set some default options on the contract
155 | contract.options.gas = 1500000
156 | contract.options.from = from
157 | }
158 |
159 | return contract
160 | }
161 |
162 | return { web3, accounts, deploy, server }
163 | }
164 |
165 | // Start the server if it hasn't been already...
166 | async function server(web3, provider) {
167 | try {
168 | // Hack to prevent "connection not open on send" error when using websockets
169 | web3.setProvider(provider.replace(/^ws/, 'http'))
170 | await web3.eth.net.getId()
171 | web3.setProvider(provider)
172 | return
173 | } catch (e) {
174 | /* Ignore */
175 | }
176 |
177 | var port = '7545'
178 | if (String(provider).match(/:([0-9]+)$/)) {
179 | port = provider.match(/:([0-9]+)$/)[1]
180 | }
181 | var server = Ganache.server()
182 | await server.listen(port)
183 | return server
184 | }
185 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_AddKey.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | import { KeyPurpose, KeyTypes } from 'actions/Identity'
8 |
9 | class AddKey extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | keyPurpose: '1',
14 | keyType: '1',
15 | key: '',
16 | publicKey: '',
17 | privateKey: ''
18 | }
19 | }
20 |
21 | componentWillReceiveProps(nextProps) {
22 | if (this.props.response !== 'success' && nextProps.response === 'success') {
23 | this.setState({ shouldClose: true, submitted: true })
24 | }
25 | if (
26 | this.props.response !== 'submitted' &&
27 | nextProps.response === 'submitted'
28 | ) {
29 | this.setState({ loading: true })
30 | }
31 | if (this.props.response !== 'error' && nextProps.response === 'error') {
32 | this.setState({ error: true })
33 | }
34 | }
35 |
36 | render() {
37 | return (
38 | this.props.onClose()}
44 | >
45 |
46 | Add a Key to an Identity:
47 | {this.props.wrongOwner && (
48 |
49 | {`Active wallet does not own this identity`}
50 |
51 | )}
52 | {this.state.error && (
53 |
54 | Error! Please try again.
55 |
56 | )}
57 |
133 |
134 | {
137 | this.props.addKey({
138 | purpose: this.state.keyPurpose,
139 | keyType: this.state.keyType,
140 | key: this.state.key,
141 | identity: this.props.identity
142 | })
143 | }}
144 | >
145 | Add Key
146 |
147 |
148 |
149 | )
150 | }
151 |
152 | generateKeys() {
153 | var privateKey = web3.utils.randomHex(32)
154 | for (var i = privateKey.length; i < 66; i++) {
155 | privateKey += '0'
156 | }
157 | var publicKey = web3.eth.accounts.privateKeyToAccount(privateKey).address
158 | var key = web3.utils.sha3(publicKey)
159 |
160 | this.setState({ privateKey, publicKey, key })
161 | }
162 | }
163 |
164 | export default AddKey
165 |
--------------------------------------------------------------------------------
/src/pages/_Init.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import Modal from 'components/Modal'
5 |
6 | import { sendFromNode } from 'actions/Network'
7 | import { reset, deployIdentityContract, addKey } from 'actions/Identity'
8 | import { selectAccount } from 'actions/Wallet'
9 |
10 | class Event extends Component {
11 | constructor(props) {
12 | super(props)
13 | this.state = { modal: false, logs: [] }
14 | this.stage = 0
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | var nodeAccounts = nextProps.network.accounts,
19 | walletAccounts = nextProps.wallet.accounts,
20 | balances = nextProps.wallet.balances
21 |
22 | if (
23 | this.stage === 0 &&
24 | nextProps.network.status === 'connected' &&
25 | nodeAccounts.length > 2 &&
26 | walletAccounts.length > 2 &&
27 | balances[walletAccounts[0]] &&
28 | balances[walletAccounts[0]].eth === '0'
29 | ) {
30 | window.localStorage.clear()
31 | this.props.reset()
32 | this.next('Add some balance to account 1...')
33 | setTimeout(() => {
34 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[0], '2')
35 | }, 500)
36 | } else if (this.stage === 1 && balances[walletAccounts[1]].eth === '0') {
37 | this.next('Add some balance to account 2...')
38 | setTimeout(() => {
39 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[1], '2')
40 | }, 500)
41 | } else if (this.stage === 2 && balances[walletAccounts[2]].eth === '0') {
42 | this.next('Add some balance to account 3...')
43 | setTimeout(() => {
44 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[2], '2')
45 | }, 500)
46 | } else if (this.stage === 3 && balances[walletAccounts[2]].eth === '0') {
47 | this.next('Add Origin certifier...')
48 | setTimeout(() => {
49 | this.props.selectAccount(walletAccounts[1])
50 | this.props.deployIdentityContract(
51 | 'Origin',
52 | 'certifier',
53 | 'https://erc725.originprotocol.com/fb-auth',
54 | false,
55 | 'facebook',
56 | [
57 | {
58 | uri: 'https://erc725.originprotocol.com/fb-auth',
59 | icon: 'facebook',
60 | claimType: '3'
61 | },
62 | {
63 | uri: 'https://erc725.originprotocol.com/twitter-auth',
64 | icon: 'twitter',
65 | claimType: '4'
66 | },
67 | {
68 | uri: 'https://erc725.originprotocol.com/github-auth',
69 | icon: 'github',
70 | claimType: '5'
71 | },
72 | {
73 | uri: 'https://erc725.originprotocol.com/google-auth',
74 | icon: 'google',
75 | claimType: '6'
76 | },
77 | {
78 | uri: 'https://erc725.originprotocol.com/linkedin-auth',
79 | icon: 'linkedin',
80 | claimType: '9'
81 | }
82 | ]
83 | )
84 | }, 500)
85 | } else if (
86 | this.stage === 4 &&
87 | this.props.createIdentityResponse !== 'success' &&
88 | nextProps.createIdentityResponse === 'success'
89 | ) {
90 | this.next('Add Claim Signer key...')
91 | setTimeout(() => {
92 | var fb = this.props.identity.identities.find(i => i.name === 'Origin')
93 | this.props.addKey({
94 | purpose: '3',
95 | keyType: '1',
96 | key:
97 | '0xc8d7a2a9478bcb765761e3a42304686d092e0a1a64e80489910bd4bc946ed7d1',
98 | identity: fb.address
99 | })
100 | }, 500)
101 | } else if (
102 | this.stage === 5 &&
103 | this.props.addKeyResponse !== 'success' &&
104 | nextProps.addKeyResponse === 'success'
105 | ) {
106 | this.next('Done!')
107 | this.props.selectAccount(walletAccounts[0])
108 | setTimeout(() => {
109 | this.setState({ shouldClose: true })
110 | }, 1500)
111 | }
112 | }
113 |
114 | next(msg) {
115 | this.stage += 1
116 | this.setState({ modal: true, logs: [...this.state.logs, msg] })
117 | }
118 |
119 | render() {
120 | if (!this.state.modal) {
121 | return null
122 | }
123 | return (
124 | {
126 | this.setState({ modal: false })
127 | if (this.props.onClose) {
128 | this.props.onClose()
129 | }
130 | }}
131 | shouldClose={this.state.shouldClose}
132 | style={{ maxWidth: 375 }}
133 | >
134 |
135 |
Initialize
136 | {this.state.logs.map((log, idx) =>
{log}
)}
137 |
138 |
139 | )
140 | }
141 | }
142 |
143 | const mapStateToProps = state => ({
144 | wallet: state.wallet,
145 | event: state.event,
146 | network: state.network,
147 | identity: state.identity,
148 | createIdentityResponse: state.identity.createIdentityResponse,
149 | addKeyResponse: state.identity.addKeyResponse
150 | })
151 |
152 | const mapDispatchToProps = dispatch => ({
153 | sendFromNode: (...args) => dispatch(sendFromNode(...args)),
154 | deployIdentityContract: (...args) =>
155 | dispatch(deployIdentityContract(...args)),
156 | addKey: (...args) => dispatch(addKey(...args)),
157 | selectAccount: (...args) => dispatch(selectAccount(...args)),
158 | reset: () => dispatch(reset())
159 | })
160 |
161 | export default connect(mapStateToProps, mapDispatchToProps)(Event)
162 |
--------------------------------------------------------------------------------
/src/actions/Network.js:
--------------------------------------------------------------------------------
1 | import keyMirror from 'utils/keyMirror'
2 | import balance from 'utils/balance'
3 |
4 | import { loadWallet } from './Wallet'
5 |
6 | export const NetworkConstants = keyMirror(
7 | {
8 | CHANGE: null,
9 | CHANGE_SUCCESS: null,
10 | CHANGE_ERROR: null,
11 |
12 | UPDATE_BALANCE: null,
13 | UPDATE_BALANCE_SUCCESS: null,
14 | UPDATE_BALANCE_ERROR: null,
15 |
16 | FETCH_ACCOUNTS: null,
17 | FETCH_ACCOUNTS_SUCCESS: null,
18 |
19 | FETCH_LOCAL_WALLET: null,
20 | FETCH_LOCAL_WALLET_SUCCESS: null,
21 |
22 | SELECT_ACCOUNT: null,
23 | SET_PROVIDER: null,
24 | SET_IPFS: null,
25 |
26 | SEND_FROM_NODE: null,
27 | SEND_FROM_NODE_SUCCESS: null,
28 | SEND_FROM_NODE_ERROR: null,
29 |
30 | SEND_FROM_ACCOUNT: null,
31 | SEND_FROM_ACCOUNT_SUCCESS: null,
32 | SEND_FROM_ACCOUNT_ERROR: null
33 | },
34 | 'NETWORK'
35 | )
36 |
37 | export function init() {
38 | return async function(dispatch, getState) {
39 | var state = getState()
40 | dispatch({ type: NetworkConstants.CHANGE })
41 | dispatch(loadWallet())
42 |
43 | var accounts = [],
44 | balanceWei
45 | var id = await web3.eth.net.getId().catch(() => {
46 | dispatch({
47 | type: NetworkConstants.CHANGE_ERROR,
48 | error: 'Network unavailable'
49 | })
50 | return
51 | })
52 | if (!id) {
53 | return
54 | }
55 |
56 | var accountsRaw = await web3.eth.getAccounts()
57 |
58 | for (let hash of accountsRaw) {
59 | balanceWei = await web3.eth.getBalance(hash)
60 | accounts.push({
61 | hash,
62 | balanceWei,
63 | balance: balance(balanceWei, state.wallet.exchangeRates)
64 | })
65 | }
66 |
67 | dispatch({
68 | type: NetworkConstants.CHANGE_SUCCESS,
69 | id,
70 | accounts
71 | })
72 | }
73 | }
74 |
75 | export function fetchAccounts() {
76 | return async function(dispatch) {
77 | dispatch({ type: NetworkConstants.FETCH_ACCOUNTS })
78 |
79 | var accounts = []
80 | var accountsRaw = await web3.eth.getAccounts()
81 | for (let hash of accountsRaw) {
82 | var balanceWei = await web3.eth.getBalance(hash)
83 | accounts.push({
84 | hash,
85 | balanceWei,
86 | balance: web3.utils.fromWei(balanceWei, 'ether')
87 | })
88 | }
89 |
90 | dispatch({ type: NetworkConstants.FETCH_ACCOUNTS_SUCCESS, accounts })
91 | }
92 | }
93 |
94 | export function updateBalance(account) {
95 | return async function(dispatch, getState) {
96 | var state = getState()
97 | dispatch({ type: NetworkConstants.UPDATE_BALANCE })
98 |
99 | var balanceWei = await web3.eth.getBalance(account)
100 |
101 | dispatch({
102 | type: NetworkConstants.UPDATE_BALANCE_SUCCESS,
103 | account,
104 | balance: balance(balanceWei, state.wallet.exchangeRates)
105 | })
106 | }
107 | }
108 |
109 | export function sendFromNode(from, to, value) {
110 | return function(dispatch) {
111 | dispatch({ type: NetworkConstants.SEND_FROM_NODE, from, to, value })
112 |
113 | web3.eth
114 | .sendTransaction({
115 | from,
116 | to,
117 | value: web3.utils.toWei(value, 'ether'),
118 | gas: 4612388
119 | })
120 | .on('transactionHash', hash => {
121 | dispatch({ type: 'LOG', message: 'transactionHash', hash })
122 | })
123 | .on('receipt', receipt => {
124 | dispatch({ type: 'LOG', message: 'receipt', receipt })
125 | })
126 | .on('confirmation', function(num, receipt) {
127 | if (num === 1) {
128 | dispatch({ type: NetworkConstants.SEND_FROM_NODE_SUCCESS, receipt })
129 | dispatch(updateBalance(from))
130 | dispatch(updateBalance(to))
131 | }
132 | })
133 | .on('error', error => {
134 | dispatch({ type: NetworkConstants.SEND_FROM_NODE_ERROR, error })
135 | })
136 | }
137 | }
138 |
139 | export function sendFromAccount(from, to, value) {
140 | return async function(dispatch, getState) {
141 | var state = getState()
142 | var chainId = state.network.id
143 | var account = state.wallet.raw[from]
144 | var valEth = value
145 | if (state.wallet.currency !== 'eth') {
146 | valEth = String(
147 | Number(value) / state.wallet.exchangeRates[state.wallet.currency]
148 | )
149 | }
150 |
151 | dispatch({ type: NetworkConstants.SEND_FROM_ACCOUNT, from, to, value })
152 |
153 | var signedTx = await account.signTransaction({
154 | from: account.address,
155 | to,
156 | gas: 4612388,
157 | value: web3.utils.toWei(valEth, 'ether'),
158 | chainId: chainId > 10 ? 1 : chainId
159 | })
160 |
161 | web3.eth
162 | .sendSignedTransaction(signedTx.rawTransaction)
163 | .on('error', error => {
164 | dispatch({
165 | type: NetworkConstants.SEND_FROM_ACCOUNT,
166 | message: error.message
167 | })
168 | })
169 | .on('transactionHash', hash => {
170 | dispatch({ type: 'LOG', message: 'transactionHash', hash })
171 | })
172 | .on('receipt', receipt => {
173 | dispatch({ type: 'LOG', message: 'receipt', receipt })
174 | })
175 | .on('confirmation', num => {
176 | if (num === 1) {
177 | dispatch({ type: NetworkConstants.SEND_FROM_ACCOUNT_SUCCESS })
178 | dispatch(updateBalance(from))
179 | dispatch(updateBalance(to))
180 | }
181 | })
182 | }
183 | }
184 |
185 | export function setProvider(provider) {
186 | return async function(dispatch, getState) {
187 | var state = getState()
188 | if (state.network.provider === provider) {
189 | return
190 | }
191 | web3.eth.setProvider(provider)
192 | dispatch({ type: NetworkConstants.SET_PROVIDER, provider })
193 | dispatch(init())
194 | }
195 | }
196 |
197 | export function selectAccount(hash) {
198 | return { type: NetworkConstants.SELECT_ACCOUNT, hash }
199 | }
200 | export function setIpfs(gateway, api) {
201 | return { type: NetworkConstants.SET_IPFS, gateway, api }
202 | }
203 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/ClaimDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | fromRpcSig,
4 | ecrecover,
5 | toBuffer,
6 | bufferToHex,
7 | pubToAddress
8 | } from 'ethereumjs-util'
9 |
10 | import { claimType, scheme } from 'actions/Identity'
11 |
12 | import Identity from 'contracts/ClaimHolder'
13 | import Modal from 'components/Modal'
14 | import Row from 'components/DetailRow'
15 |
16 | class ClaimDetail extends Component {
17 | constructor(props) {
18 | super(props)
19 | this.state = {}
20 | }
21 |
22 | async componentDidMount() {
23 | var { address } = this.props.identity
24 | var identity = new web3.eth.Contract(Identity.abi, address)
25 | var claim = await identity.methods.getClaim(this.props.claimId).call()
26 | if (claim) {
27 | if (claim.scheme === '4') {
28 | this.setState({ claim, hasKey: true })
29 | return
30 | }
31 | var expectedData = web3.utils.soliditySha3('Verified OK')
32 | var hashedSignature = web3.utils.soliditySha3(
33 | address,
34 | claim.claimType,
35 | claim.data
36 | )
37 | const prefixedMsg = web3.eth.accounts.hashMessage(hashedSignature)
38 |
39 | var dataBuf = toBuffer(prefixedMsg)
40 | var sig = fromRpcSig(claim.signature)
41 | var recovered = ecrecover(dataBuf, sig.v, sig.r, sig.s)
42 | var recoveredKeyBuf = pubToAddress(recovered)
43 | var recoveredKey = bufferToHex(recoveredKeyBuf)
44 | var hashedRecovered = web3.utils.soliditySha3(recoveredKey)
45 |
46 | var issuer = new web3.eth.Contract(Identity.abi, claim.issuer)
47 | try {
48 | var hasKey = await issuer.methods
49 | .keyHasPurpose(hashedRecovered, 3)
50 | .call()
51 | } catch (e) {
52 | /*Ignore*/
53 | }
54 |
55 | this.setState({
56 | claim,
57 | recoveredKey,
58 | hasKey,
59 | hashedSignature,
60 | hashedRecovered,
61 | expectedData
62 | })
63 | }
64 | }
65 |
66 | render() {
67 | const { claim, hasKey } = this.state
68 | if (!claim) {
69 | return null
70 | }
71 |
72 | return (
73 | this.props.onClose()}
77 | >
78 | Claim Detail
79 |
80 |
81 |
85 | {this.props.identity.address}
86 |
87 |
91 | {claim.issuer}
92 |
93 |
97 | {this.props.claimId}
98 |
99 | {`${claimType(claim.claimType)} (${claim.claimType})`}
103 | {`${scheme(claim.scheme)} (${claim.scheme})`}
107 |
111 | {web3.utils.hexToAscii(claim.data)}
112 |
113 |
117 | {claim.uri}
118 |
119 |
123 | {this.state.hashedSignature}
124 |
125 |
129 | {claim.signature}
130 |
131 |
136 | {this.state.recoveredKey}
137 |
138 |
142 | {this.state.hashedRecovered}
143 |
144 |
148 |
153 | {hasKey ? 'Yes' : 'No'}
154 |
155 |
156 |
157 |
158 |
159 | )
160 | }
161 | }
162 |
163 | export default ClaimDetail
164 |
165 | require('react-styl')(`
166 | th.label
167 | white-space: no-wrap
168 | td.info a
169 | color: #999
170 | &:hover
171 | color: #000
172 | &.active
173 | color: green
174 | .no-wrap
175 | white-space: nowrap
176 | .bt td, .bt th
177 | border-top-width: 3px
178 | .wb
179 | word-break: break-all
180 | .ellipsis
181 | position: absolute
182 | width: 100%
183 | overflow: hidden
184 | white-space: nowrap
185 | text-overflow: ellipsis
186 | `)
187 |
--------------------------------------------------------------------------------
/test/Identity.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import helper from './_helper'
3 |
4 | describe('Identity', async function() {
5 | var web3, accounts, deploy, acctSha3, randomHex
6 | var UserIdentity
7 |
8 | before(async function() {
9 | ({ web3, deploy, accounts, web3: { utils: { randomHex } } } = await helper(
10 | `${__dirname}/../contracts/`
11 | ))
12 |
13 | UserIdentity = await deploy('ClaimHolder', { from: accounts[0] })
14 | acctSha3 = web3.utils.keccak256(accounts[0])
15 | })
16 |
17 | describe('Pre-Auth Identity', async function() {
18 | it('should deploy successfully', async function() {
19 | var sig = randomHex(10)
20 | var data = randomHex(10)
21 | var url = "1234567890"
22 | await deploy('Identity', { from: accounts[0], args: [
23 | // [1], [3], [accounts[0]], sig, data, url, [sig.length-2], [data.length-2], [url.length]
24 | [1], [3], [accounts[0]], sig, data, url, [10], [10], [10]
25 | ] })
26 | })
27 | })
28 |
29 | describe('Keys', async function() {
30 | it('should set a default MANAGEMENT_KEY', async function() {
31 | var res = await UserIdentity.methods.getKey(acctSha3).call()
32 | assert.equal(res.purpose, '1')
33 | assert.equal(res.keyType, '1')
34 | assert.equal(res.key, acctSha3)
35 | })
36 |
37 | it('should respond to getKeyPurpose', async function() {
38 | var res = await UserIdentity.methods.getKeyPurpose(acctSha3).call()
39 | assert.equal(res, '1')
40 | })
41 |
42 | it('should respond to getKeysByPurpose', async function() {
43 | var res = await UserIdentity.methods.getKeysByPurpose(1).call()
44 | assert.deepEqual(res, [acctSha3])
45 | })
46 |
47 | it('should implement addKey', async function() {
48 | var newKey = web3.utils.randomHex(32)
49 | var res = await UserIdentity.methods.addKey(newKey, 1, 1).send()
50 | assert(res.events.KeyAdded)
51 |
52 | var getKey = await UserIdentity.methods.getKey(newKey).call()
53 | assert.equal(getKey.key, newKey)
54 | })
55 |
56 | it('should not allow an existing key to be added', async function() {
57 | try {
58 | await UserIdentity.methods.addKey(acctSha3, 1, 1).send()
59 | assert(false)
60 | } catch (e) {
61 | assert(e.message.match(/revert/))
62 | }
63 | })
64 |
65 | it('should not allow sender without MANAGEMENT_KEY to addKey', async function() {
66 | try {
67 | await UserIdentity.methods.addKey(web3.utils.randomHex(32), 1, 1).send({
68 | from: accounts[1]
69 | })
70 | assert(false)
71 | } catch (e) {
72 | assert(e.message.match(/revert/))
73 | }
74 | })
75 | })
76 |
77 | describe('Claims', async function() {
78 |
79 | it('should allow a claim to be added by management account', async function() {
80 | var response = await UserIdentity.methods
81 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
82 | .send()
83 | assert(response.events.ClaimAdded)
84 | })
85 |
86 | it('should disallow new claims from unrecognized accounts', async function() {
87 | try {
88 | await UserIdentity.methods
89 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
90 | .send({ from: accounts[2] })
91 | assert(false)
92 | } catch (e) {
93 | assert(e.message.match(/revert/))
94 | }
95 | })
96 |
97 | it('should have 1 claim by type', async function() {
98 | var byTypeRes = await UserIdentity.methods.getClaimIdsByType(1).call()
99 | assert.equal(byTypeRes.length, 1)
100 | })
101 |
102 | it('should respond to getClaim', async function() {
103 | var claimId = web3.utils.soliditySha3(accounts[0], 1)
104 | var claim = await UserIdentity.methods.getClaim(claimId).call()
105 | assert.equal(claim.claimType, "1")
106 | })
107 |
108 | // it('should respond to isClaimValid', async function() {
109 | // var claimId = web3.utils.soliditySha3(accounts[0], 1)
110 | // var valid = await UserIdentity.methods.isClaimValid(claimId).call()
111 | // assert(valid)
112 | // })
113 |
114 | it('should allow claim to be removed', async function() {
115 | var claimId = web3.utils.soliditySha3(accounts[0], 1)
116 | var response = await UserIdentity.methods
117 | .removeClaim(claimId)
118 | .send({ from: accounts[0] })
119 | assert(response.events.ClaimRemoved)
120 |
121 | var claim = await UserIdentity.methods.getClaim(claimId).call()
122 | assert.equal(claim.claimType, "0")
123 | })
124 | })
125 |
126 | describe('Executions', async function() {
127 | it('should allow any account to execute actions', async function() {
128 | var addClaimAbi = await UserIdentity.methods
129 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
130 | .encodeABI()
131 |
132 | var response = await UserIdentity.methods
133 | .execute(UserIdentity.options.address, 0, addClaimAbi)
134 | .send({
135 | from: accounts[2]
136 | })
137 |
138 | assert(response.events.ExecutionRequested)
139 | assert(!response.events.Approved)
140 | assert(!response.events.Executed)
141 | })
142 |
143 | it('should auto-approve executions from MANAGEMENT_KEYs', async function() {
144 | var addClaimAbi = await UserIdentity.methods
145 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
146 | .encodeABI()
147 |
148 | var response = await UserIdentity.methods
149 | .execute(UserIdentity.options.address, 0, addClaimAbi)
150 | .send({
151 | from: accounts[0]
152 | })
153 |
154 | assert(response.events.ExecutionRequested)
155 | assert(response.events.Approved)
156 | assert(response.events.ClaimAdded)
157 | assert(response.events.Executed)
158 | })
159 | })
160 |
161 | describe('Approvals', async function() {
162 | it('should allow MANAGEMENT_KEYs to approve executions', async function() {
163 | var addClaimAbi = await UserIdentity.methods
164 | .addClaim(1, 2, accounts[2], randomHex(32), randomHex(32), 'abc.com')
165 | .encodeABI()
166 |
167 | var response = await UserIdentity.methods
168 | .execute(UserIdentity.options.address, 0, addClaimAbi)
169 | .send({ from: accounts[2] })
170 |
171 | assert(response.events.ExecutionRequested)
172 | assert(!response.events.Approved)
173 |
174 | var id = response.events.ExecutionRequested.returnValues.executionId;
175 |
176 | var approval = await UserIdentity.methods.approve(id, true)
177 | .send({ from: accounts[0] })
178 |
179 | assert(approval.events.Approved)
180 | assert(approval.events.ClaimAdded)
181 | assert(approval.events.Executed)
182 | })
183 |
184 | it('should allow ACTION_KEYs to approve executions')
185 | it('should not allow CLAIM_SIGNER_KEYs to approve executions')
186 | it('should not be able to approve an already executed execution')
187 | it('should not be able to approve a non-existant execution')
188 | })
189 | })
190 |
--------------------------------------------------------------------------------
/src/pages/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Switch, Route, Link } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 |
5 | import Console from './console'
6 | import Identity from './identity'
7 | import Versions from './_Versions'
8 | import Init from './_Init'
9 |
10 | import { init } from 'actions/Network'
11 | import AccountChooser from 'components/AccountChooser'
12 |
13 | import { selectAccount, setCurrency, loadWallet } from 'actions/Wallet'
14 |
15 | class App extends Component {
16 | constructor(props) {
17 | super(props)
18 | this.state = {}
19 | }
20 |
21 | componentDidMount() {
22 | this.props.initNetwork()
23 | }
24 |
25 | componentWillUnmount() {
26 | clearTimeout(this.hideNotice)
27 | }
28 |
29 | componentDidUpdate(prevProps) {
30 | if (
31 | window.innerWidth <= 575 &&
32 | this.props.location !== prevProps.location
33 | ) {
34 | window.scrollTo(0, 0)
35 | }
36 | }
37 |
38 | componentWillReceiveProps(nextProps) {
39 | // If no accounts are present, pre-populate for an easier demo experience.
40 | if (
41 | !this.props.wallet.loaded &&
42 | nextProps.wallet.loaded &&
43 | !nextProps.wallet.activeAddress
44 | ) {
45 | window.sessionStorage.privateKeys = JSON.stringify([
46 | '0x1aae4f8918c2c1fa3f911415491a49e541a528233da3a54df21e7eea5c675cd9',
47 | '0x7a8be97032a5c719d2cea4e4adaed0620e9fa9e49e2ccf689daf9180e3638f93',
48 | '0x85a676919234e90007b20bf3ae6b54b455b62b42bf298ac03669d164e4689c49'
49 | ])
50 | this.props.loadWallet()
51 | this.setState({ preloaded: true })
52 | this.hideNotice = setTimeout(
53 | () => this.setState({ preloaded: false }),
54 | 3000
55 | )
56 | }
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
this.props.history.push('/')} />
63 |
64 |
65 |
this.setState({ toggled: false })}
69 | >
70 | ERC 725
71 |
72 |
73 | Demo implementation by
74 |
75 |
79 |
80 |
81 |
85 | this.setState({
86 | toggled: this.state.toggled ? false : true
87 | })
88 | }
89 | >
90 |
91 |
92 |
97 |
98 | {this.props.account &&
99 | this.props.wallet && (
100 |
101 | this.props.selectAccount(a)}
106 | setCurrency={c => this.props.setCurrency(c)}
107 | />
108 |
109 | )}
110 |
111 |
112 |
113 |
114 |
115 |
116 | {!this.state.preloaded ? null : (
117 |
130 | )}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
156 |
157 |
158 | )
159 | }
160 | }
161 |
162 | const mapStateToProps = state => ({
163 | account: state.wallet.activeAddress,
164 | balance: state.wallet.balances[state.wallet.activeAddress],
165 | wallet: state.wallet,
166 | nodeAccounts: state.network.accounts
167 | })
168 |
169 | const mapDispatchToProps = dispatch => ({
170 | initNetwork: () => {
171 | dispatch(init())
172 | },
173 | loadWallet: () => {
174 | dispatch(loadWallet())
175 | },
176 | selectAccount: hash => dispatch(selectAccount(hash)),
177 | setCurrency: currency => dispatch(setCurrency(currency))
178 | })
179 |
180 | export default connect(mapStateToProps, mapDispatchToProps)(App)
181 |
182 | require('react-styl')(`
183 | table.table
184 | thead tr th
185 | border-top: 0
186 | .btn-sm
187 | padding: 0.125rem 0.375rem
188 | .navbar
189 | border-bottom: 1px solid #E5E9EF;
190 | .navbar-light .navbar-text .dropdown-item.active,
191 | .navbar-light .navbar-text .dropdown-item:active
192 | color: #fff;
193 | .pointer
194 | cursor: pointer
195 | .no-wrap
196 | white-space: nowrap
197 | .footer
198 | display: flex
199 | align-items: center;
200 | color: #999;
201 | margin: 1rem 0;
202 | padding-top: 1rem;
203 | border-top: 1px solid #eee;
204 | font-size: 14px;
205 | a
206 | color: #999;
207 | .middle
208 | flex: 1
209 | text-align: center
210 | .right
211 | flex: 1
212 | text-align: right
213 | .powered-by
214 | flex: 1
215 | font-size: 14px;
216 | letter-spacing: -0.01rem;
217 | img
218 | opacity: .4;
219 | height: 12px;
220 | margin-top: -2px
221 | margin-right: 0.25rem
222 | `)
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Head to https://www.originprotocol.com/developers to learn more about what we're building and how to get involved.
4 |
5 | # Origin Identity Playground
6 |
7 | ----
8 |
9 | **Warning!** This repo contains only our protoyping work around ERC 725. It is now out of date and **should not be used**. Our current identity contracts are [here](https://github.com/OriginProtocol/origin/tree/master/origin-contracts/contracts/identity).
10 |
11 | ----
12 |
13 | This app is a working implementation of the [ERC 725](https://github.com/ethereum/EIPs/issues/725) and [ERC 735](https://github.com/ethereum/EIPs/issues/735) proposals for managing a unique identity on the blockchain.
14 |
15 | Using ERC 725, a smart contract can protect function calls from being executed unless the sender has a verified claim
16 | from a trusted issuer.
17 |
18 | ## Live Demo
19 |
20 | You can try a deployed version of this app at http://erc725.originprotocol.com/#/
21 |
22 | It has several Certifiers pre-populated that are not present when running the demo locally.
23 |
24 | The [screenshot walkthrough](#walkthrough) below shows an example of verifying a claims of a person's identity.
25 |
26 | ## Explanation
27 |
28 | Imagine we want to deploy a Listing contract to sell a concert ticket, but only allow interactions from
29 | users with a verified email address. How can we accomplish this with ERC 725?
30 |
31 | First, lets define the entities that will be interacting:
32 |
33 | * The _Consumer_ is an identity who wants to buy the ticket.
34 | * The _Issuer_ is an identity which issues claims of type 'EMAIL_VERIFIED'.
35 | * The _Listing_ will only allow _Consumers_ with an _EMAIL_VERIFIED_ claim from an _Issuer_ they trust.
36 |
37 | This leaves us with a few questions...
38 |
39 | 1. How does the trusted Issuer verify an email address?
40 | 2. How does the Consumer get an EMAIL_VERIFIED claim onto their Identity?
41 | 3. How can the Listing verify that the Consumer has an EMAIL_VERIFIED claim from a trusted Issuer?
42 |
43 | To answer these questions, lets go through the process of setting up all the required contracts and services, starting
44 | with the Issuer.
45 |
46 | The job of the Issuer is to act as a trusted third party. In the future, trusted organizations may deploy their own
47 | Issuer identity contracts onto the blockchain, which third parties can then trust. Origin plan to offer their own basic
48 | Issuer contracts for verifying email addresses, phone numbers, Facebook accounts, Twitter accounts, etc. Third parties
49 | will then be able to trust that these Origin Issuer contracts only issue claims if they are, in fact, true.
50 |
51 | How will an email verifier work? A typical verification service may involve an application, for example
52 | http://example.com/verify-email. This application will have a standard interface for verifying an email
53 | address, whereby a user is sent an email with a special code which they then submit back to the application. Now that
54 | the email address has been verified, it can be signed with a private key known only to the email verifier app. The
55 | corresponding public key is on the issuer's identity. This is how a claim is verified.
56 |
57 | More explanation to follow...
58 |
59 | ## Walkthrough
60 |
61 | 1. Screen upon loading
62 |
63 | 
64 |
65 | 2. Confirm that the first wallet ID is active. (`0x313AaD` in our screenshot) We are playing the role of a person who desires a blockchain identity.
66 |
67 | 2. Click "Add an Identity" and deploy an identity contract with name "Alice".
68 |
69 | 
70 |
71 | You can see the address of the contract, as well as the wallet ID of the owner.
72 |
73 | 3. Switch the active wallet to the second. (`0x56BEaa` in our screenshot) We are now playing the role of a service that can verify a GitHub account.
74 |
75 | 4. Click "Add a Certifier" and deploy a certifier contract called "Github". For now we'll use an example URL for our service.
76 |
77 | 
78 |
79 | Again, you should see the address of this contract, and the walled ID of the owner of this contract.
80 |
81 | 5. Switch the active wallet to the third. (`0xCd5e74` in our screenshot) We are now playing the role of an eBay-like application that wants to restrict access to only people with verified Github accounts. (A marketplace for developers, perhaps!)
82 |
83 | 6. Click "Add a Protected Contract" and deploy a contract called "Listing" with certifier of "Github". This is the contract which will be limited to interacting to people with verified Github accounts.
84 |
85 | 
86 |
87 | 7. The screen should now look like this.
88 |
89 | 
90 |
91 | 8. Switch to the first wallet, belonging to "Alice".
92 | 9. Click on the "Listing" contract.
93 | 10. Click on "Run Protected Method", and switch the desired Claim Type to "Has GitHub",
94 |
95 | 
96 |
97 | 11. After clicking on "Check Claim", you should see that the claim is returned as **ClaimInvalid**. At this point, Alice has no proof that she has a GitHub account.
98 |
99 | 
100 |
101 | 12. Switch to the second wallet, and click on "GitHub" under "Certifiers".
102 |
103 | 13. On right column, click on the "+" next to "Claims" to add a claim.
104 |
105 | 14. Switch the "Claim Type" to "Has Github" and click "Add Claim".
106 |
107 | 
108 |
109 | 15. Switch to the first wallet, and click on the "Alice" identity.
110 |
111 | 16. In right column, you should see the claim by our "Github" Certifier (from pervious step) that she has a GitHub account. Click "Approve" to accept this claim to Alice's identity.
112 |
113 | 
114 |
115 | 17. Alice now has on-chain proof of her GitHub!
116 |
117 | 
118 |
119 | 18. Now click the "Listing" under "Protected Contracts", and then click on "Run Protected Method". Change the "Claim Type" to "Has Github"
120 |
121 | 19. You should see that this claim is returned as **ClaimValid***.
122 |
123 | 
124 |
125 | Alice is ready to start shopping!
126 |
127 |
128 | ## Installation
129 |
130 | npm install
131 | npm start
132 |
133 | ## Tests
134 |
135 | npm run test
136 |
137 | ## Related
138 |
139 | * https://github.com/mirceapasoi/erc725-735
140 |
141 | ## Credits
142 |
143 | * [JosefJ](https://github.com/JosefJ) for the original contract [implementation](https://github.com/JosefJ/IdentityContract)
144 |
--------------------------------------------------------------------------------
/src/pages/identity/ClaimCheckerDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import { Switch, Route, NavLink } from 'react-router-dom'
4 |
5 | import {
6 | getEvents,
7 | checkClaim,
8 | removeVerifier,
9 | claimType
10 | } from 'actions/Identity'
11 |
12 | import { selectAccount } from 'actions/Wallet'
13 |
14 | import Events from './_Events'
15 | import CheckClaim from './modals/_CheckClaim'
16 |
17 | class ProtectedDetail extends Component {
18 | constructor(props) {
19 | super(props)
20 | this.state = { mode: 'summary' }
21 | this.props.getEvents('ClaimVerifier', props.match.params.id)
22 | }
23 |
24 | componentWillReceiveProps(nextProps) {
25 | if (this.props.match.params.id !== nextProps.match.params.id) {
26 | this.props.getEvents('ClaimVerifier', nextProps.match.params.id)
27 | }
28 | }
29 |
30 | render() {
31 | var identities = this.props.identities.filter(i => i.type !== 'certifier')
32 | var address = this.props.match.params.id
33 |
34 | var verifier = this.props.verifiers.find(i => i.address === address)
35 | if (!verifier) {
36 | return null
37 | }
38 | var isOwner = verifier.owner === this.props.wallet.activeAddress
39 |
40 | return (
41 | <>
42 |
79 |
80 | (
83 | <>
84 | {`Address: ${verifier.address}`}
85 | {`Owner: ${verifier.owner}`}
86 |
87 |
88 |
89 | {
94 | if (isOwner) {
95 | this.props.removeVerifier(verifier.address)
96 | this.setState({ activeIdentity: null })
97 | }
98 | }}
99 | >
100 | Remove Contract
101 |
102 |
103 | >
104 | )}
105 | />
106 | (
109 |
113 | )}
114 | />
115 | (
119 | <>
120 |
121 | this.setState({ checkClaim: true })}
124 | >
125 | {verifier.methodName || 'Check Claim via Contract'}
126 |
127 |
128 | {this.renderAttempts(this.props.events, verifier)}
129 | >
130 | )}
131 | />
132 |
133 |
134 | {this.state.checkClaim && (
135 | this.setState({ checkClaim: false })}
137 | verifier={verifier}
138 | identities={identities}
139 | checkClaim={this.props.checkClaim}
140 | response={this.props.checkClaimResponse}
141 | />
142 | )}
143 | >
144 | )
145 | }
146 |
147 | renderAttempts(events, verifier) {
148 | if (!events || !events.length) {
149 | return null
150 | }
151 |
152 | return (
153 |
154 |
155 |
156 | Block
157 | Identity
158 | Claim Type
159 | Issuer
160 | Result
161 |
162 |
163 |
164 | {events.map((evt, idx) => (
165 |
166 | {evt.blockNumber}
167 |
168 | {this.props.identity.names[evt.returnValues._identity] ||
169 | String(evt.returnValues._identity).substr(0, 8)}
170 |
171 | {claimType(verifier.claimType)}
172 |
173 | {this.props.identity.names[verifier.trustedIdentity] ||
174 | String(verifier.trustedIdentity).substr(0, 8)}
175 |
176 |
177 | {evt.event === 'ClaimValid' ? (
178 | Valid
179 | ) : (
180 | Invalid
181 | )}
182 |
183 |
184 | ))}
185 |
186 |
187 | )
188 | }
189 | }
190 |
191 | const mapStateToProps = (state, ownProps) => ({
192 | identity: state.identity,
193 | identities: [
194 | ...state.identity.identities,
195 | ...state.identity.officialIdentities
196 | ],
197 | verifiers: state.identity.claimVerifiers,
198 | events: state.identity.events,
199 | eventsResponse: state.identity.eventsResponse,
200 | checkClaimResponse: state.identity.checkClaimResponse,
201 | wallet: state.wallet,
202 | activeIdentity: ownProps.match.params.id
203 | })
204 |
205 | const mapDispatchToProps = dispatch => ({
206 | getEvents: (type, address) => dispatch(getEvents(type, address)),
207 | checkClaim: (...args) => dispatch(checkClaim(...args)),
208 | removeVerifier: (...args) => dispatch(removeVerifier(...args)),
209 | selectAccount: hash => dispatch(selectAccount(hash))
210 | })
211 |
212 | export default connect(mapStateToProps, mapDispatchToProps)(ProtectedDetail)
213 |
--------------------------------------------------------------------------------
/src/actions/Wallet.js:
--------------------------------------------------------------------------------
1 | import keyMirror from 'utils/keyMirror'
2 | import balance from 'utils/balance'
3 |
4 | export const WalletConstants = keyMirror(
5 | {
6 | LOAD: null,
7 | LOAD_SUCCESS: null,
8 | LOAD_ERROR: null,
9 |
10 | LOAD_EXTERNAL: null,
11 | LOAD_EXTERNAL_SUCCESS: null,
12 | LOAD_EXTERNAL_ERROR: null,
13 |
14 | ADD_ACCOUNT: null,
15 | ADD_ACCOUNT_SUCCESS: null,
16 | ADD_ACCOUNT_ERROR: null,
17 |
18 | SELECT_ACCOUNT: null,
19 | SELECT_ACCOUNT_SUCCESS: null,
20 | SELECT_ACCOUNT_ERROR: null,
21 |
22 | IMPORT_ACCOUNT: null,
23 | IMPORT_ACCOUNT_SUCCESS: null,
24 | IMPORT_ACCOUNT_ERROR: null,
25 |
26 | SAVE: null,
27 | SAVE_SUCCESS: null,
28 | SAVE_ERROR: null,
29 |
30 | REMOVE_ACCOUNT: null,
31 | REMOVE_ACCOUNT_SUCCESS: null,
32 | REMOVE_ACCOUNT_ERROR: null,
33 |
34 | UPDATE_BALANCE: null,
35 | UPDATE_BALANCE_SUCCESS: null,
36 | UPDATE_BALANCE_ERROR: null,
37 |
38 | SET_CURRENCY: null,
39 | LOCK_WALLET: null,
40 | UNLOCK_WALLET: null,
41 | UNLOCKED_WALLET: null
42 | },
43 | 'WALLET'
44 | )
45 |
46 | var watchMetaMaskInterval
47 | function watchMetaMask(dispatch, currentAccount) {
48 | watchMetaMaskInterval = setInterval(async function() {
49 | var accounts = await web3.eth.getAccounts()
50 | if (currentAccount !== accounts[0]) {
51 | dispatch(loadWallet(true))
52 | }
53 | }, 1000)
54 | }
55 |
56 | export function loadWallet(external) {
57 | return async function(dispatch, getState) {
58 | var state = getState()
59 |
60 | dispatch({ type: WalletConstants.LOAD, external })
61 | var wallet = web3.eth.accounts.wallet,
62 | accounts = [],
63 | balanceWei,
64 | balances = {}
65 |
66 | clearInterval(watchMetaMaskInterval)
67 |
68 | try {
69 | if (external) {
70 | web3.setProvider(state.network.browserProvider)
71 | accounts = await web3.eth.getAccounts()
72 |
73 | balanceWei = await web3.eth.getBalance(accounts[0])
74 | balances[accounts[0]] = balance(balanceWei, state.wallet.exchangeRates)
75 |
76 | web3.eth.accounts.wallet.clear()
77 | web3.eth.defaultAccount = accounts[0]
78 | dispatch({
79 | type: WalletConstants.LOAD_EXTERNAL_SUCCESS,
80 | activeAddress: accounts[0],
81 | balances
82 | })
83 | watchMetaMask(dispatch, accounts[0])
84 | return
85 | }
86 |
87 | web3.setProvider(state.network.provider)
88 |
89 | // wallet.load is expensive, so cache private keys in sessionStorage
90 | if (window.sessionStorage.privateKeys) {
91 | JSON.parse(window.sessionStorage.privateKeys).forEach(key =>
92 | web3.eth.accounts.wallet.add(key)
93 | )
94 | } else {
95 | wallet = web3.eth.accounts.wallet.load('', 'originprotocol')
96 |
97 | var accountKeys = []
98 | for (var k = 0; k < wallet.length; k++) {
99 | accountKeys.push(wallet[k].privateKey)
100 | }
101 | if (accountKeys.length) {
102 | window.sessionStorage.privateKeys = JSON.stringify(accountKeys)
103 | }
104 | }
105 |
106 | for (var i = 0; i < wallet.length; i++) {
107 | accounts.push(wallet[i].address)
108 | }
109 |
110 | for (let hash of accounts) {
111 | balanceWei = await web3.eth.getBalance(hash)
112 | balances[hash] = balance(balanceWei, state.wallet.exchangeRates)
113 | }
114 |
115 | web3.eth.defaultAccount = accounts[0]
116 |
117 | dispatch({
118 | type: WalletConstants.LOAD_SUCCESS,
119 | wallet,
120 | accounts,
121 | balances
122 | })
123 | } catch (error) {
124 | dispatch({ type: WalletConstants.LOAD_ERROR, error })
125 | }
126 | }
127 | }
128 |
129 | export function selectAccount(address) {
130 | return async function(dispatch) {
131 | dispatch({ type: WalletConstants.SELECT_ACCOUNT, address })
132 |
133 | try {
134 | var account = web3.eth.accounts.wallet[address]
135 | web3.eth.defaultAccount = address
136 |
137 | dispatch({
138 | type: WalletConstants.SELECT_ACCOUNT_SUCCESS,
139 | account,
140 | activeAddress: address
141 | })
142 | } catch (error) {
143 | dispatch({ type: WalletConstants.SELECT_ACCOUNT_ERROR, error })
144 | }
145 | }
146 | }
147 |
148 | export function addAccount() {
149 | return async function(dispatch) {
150 | dispatch({ type: WalletConstants.ADD_ACCOUNT })
151 |
152 | try {
153 | var wallet = web3.eth.accounts.wallet.create(1),
154 | account = wallet[wallet.length - 1]
155 |
156 | dispatch({
157 | type: WalletConstants.ADD_ACCOUNT_SUCCESS,
158 | wallet,
159 | account
160 | })
161 | } catch (error) {
162 | dispatch({ type: WalletConstants.ADD_ACCOUNT_ERROR, error })
163 | }
164 | }
165 | }
166 |
167 | export function importAccountFromKey(privateKey) {
168 | return async function(dispatch, getState) {
169 | var state = getState()
170 | dispatch({ type: WalletConstants.IMPORT_ACCOUNT })
171 |
172 | try {
173 | var account = web3.eth.accounts.wallet.add(privateKey)
174 | var wallet = web3.eth.accounts.wallet
175 |
176 | var balanceWei = await web3.eth.getBalance(account.address)
177 |
178 | dispatch({
179 | type: WalletConstants.IMPORT_ACCOUNT_SUCCESS,
180 | account: wallet[wallet.length - 1],
181 | wallet,
182 | balance: balance(balanceWei, state.wallet.exchangeRates)
183 | })
184 | } catch (error) {
185 | dispatch({ type: WalletConstants.IMPORT_ACCOUNT_ERROR, error })
186 | }
187 | }
188 | }
189 |
190 | export function removeAccount(hash) {
191 | return async function(dispatch) {
192 | dispatch({ type: WalletConstants.REMOVE_ACCOUNT })
193 |
194 | try {
195 | var wallet = web3.eth.accounts.wallet.remove(hash)
196 |
197 | dispatch({
198 | type: WalletConstants.REMOVE_ACCOUNT_SUCCESS,
199 | hash,
200 | wallet
201 | })
202 | } catch (error) {
203 | dispatch({ type: WalletConstants.REMOVE_ACCOUNT_ERROR, error })
204 | }
205 | }
206 | }
207 |
208 | export function saveWallet() {
209 | return async function(dispatch) {
210 | dispatch({ type: WalletConstants.SAVE })
211 |
212 | try {
213 | web3.eth.accounts.wallet.save('', 'originprotocol')
214 | dispatch({ type: WalletConstants.SAVE_SUCCESS })
215 | } catch (error) {
216 | dispatch({ type: WalletConstants.SAVE_ERROR, error })
217 | }
218 | }
219 | }
220 |
221 | export function updateBalance() {
222 | return async function(dispatch, getState) {
223 | dispatch({ type: WalletConstants.UPDATE_BALANCE })
224 |
225 | var state = getState()
226 | var account = state.wallet.activeAddress
227 |
228 | var wei = await web3.eth.getBalance(account)
229 |
230 | dispatch({
231 | type: WalletConstants.UPDATE_BALANCE_SUCCESS,
232 | account,
233 | balance: balance(wei, state.wallet.exchangeRates)
234 | })
235 | }
236 | }
237 |
238 | export function setCurrency(currency) {
239 | return {
240 | type: WalletConstants.SET_CURRENCY,
241 | currency
242 | }
243 | }
244 |
245 | export function lockWallet() {
246 | return {
247 | type: WalletConstants.LOCK_WALLET
248 | }
249 | }
250 |
251 | export function unlockWallet() {
252 | return {
253 | type: WalletConstants.UNLOCK_WALLET
254 | }
255 | }
256 |
257 | export function unlockedWallet() {
258 | return {
259 | type: WalletConstants.UNLOCKED_WALLET
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_NewClaimIssuer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | import { ClaimTypes, claimType } from 'actions/Identity'
8 |
9 | class NewClaimIssuer extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | name: '',
14 | uri: '',
15 | preAdd: false,
16 | icon: 'key',
17 | claimType: '3',
18 | stage: 'main',
19 | signerServices: []
20 | }
21 | }
22 |
23 | componentWillReceiveProps(nextProps) {
24 | if (this.props.response !== 'success' && nextProps.response === 'success') {
25 | this.setState({ shouldClose: true, submitted: true })
26 | }
27 | if (
28 | this.props.response !== 'submitted' &&
29 | nextProps.response === 'submitted'
30 | ) {
31 | this.setState({ loading: true })
32 | }
33 | }
34 |
35 | render() {
36 | return (
37 | this.props.onClose()}
44 | onOpen={() => this.nameInput.focus()}
45 | onPressEnter={() => this.onDeploy()}
46 | >
47 |
48 | {this.state.stage === 'main'
49 | ? this.renderMain()
50 | : this.renderSignerService()}
51 |
52 | )
53 | }
54 |
55 | renderMain() {
56 | var { identityType, activeAddress, identities } = this.props
57 | var otherTypeSameOwner = identities.find(
58 | i => i.type !== identityType && i.owner === activeAddress
59 | )
60 | return (
61 | <>
62 |
63 | Deploy a new Claim Issuer contract:
64 |
65 | {otherTypeSameOwner && (
66 |
67 | {`You may want to use a different wallet`}
68 |
69 | )}
70 |
83 | {this.state.signerServices.length === 0 ? null : (
84 |
85 |
86 |
87 | Claim Type
88 | Icon
89 | Service URL
90 |
91 |
92 |
93 | {this.state.signerServices.map((ss, idx) => (
94 |
95 | {claimType(ss.claimType)}
96 |
97 |
98 |
99 | {ss.uri}
100 |
101 | ))}
102 |
103 |
104 | )}
105 | {
108 | e.preventDefault()
109 | this.setState({
110 | stage: 'add-service',
111 | uri: '',
112 | icon: 'key',
113 | claimType: '7'
114 | })
115 | }}
116 | >
117 | Add Claim Signer Service
118 |
119 |
120 | this.onDeploy()}
123 | >
124 | Deploy
125 |
126 |
127 | >
128 | )
129 | }
130 |
131 | renderSignerService() {
132 | const Btn = props => (
133 | {
138 | this.setState({
139 | icon: props.icon === this.state.icon ? null : props.icon
140 | })
141 | }}
142 | >
143 |
144 |
145 | )
146 |
147 | return (
148 | <>
149 |
150 | Add Signer Service to Claim Issuer:
151 |
152 |
194 |
195 | {
198 | this.setState({ stage: 'main' })
199 | }}
200 | >
201 | Cancel
202 |
203 | {
206 | this.setState({
207 | stage: 'main',
208 | signerServices: [
209 | ...this.state.signerServices,
210 | {
211 | uri: this.state.uri,
212 | icon: this.state.icon,
213 | claimType: this.state.claimType
214 | }
215 | ]
216 | })
217 | }}
218 | >
219 | Add
220 |
221 |
222 | >
223 | )
224 | }
225 |
226 | onDeploy() {
227 | this.props.deployIdentityContract(
228 | this.state.name,
229 | this.props.identityType,
230 | this.state.uri,
231 | null,
232 | this.state.icon,
233 | this.state.signerServices
234 | )
235 | }
236 | }
237 |
238 | export default NewClaimIssuer
239 |
--------------------------------------------------------------------------------
/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | function freezeVp(e) {
5 | e.preventDefault()
6 | }
7 |
8 | export default class Modal extends Component {
9 | constructor(props) {
10 | super(props)
11 | this.state = {
12 | x: 0,
13 | y: 0,
14 | o: 1,
15 | co: 1,
16 | scale: 1,
17 | rotate: 0,
18 | anim: 'is-entering'
19 | }
20 | }
21 |
22 | componentDidMount() {
23 | this.portal = document.createElement('div')
24 | document.body.appendChild(this.portal)
25 | document.body.className += ' pl-modal-open'
26 | document.body.addEventListener('touchmove', freezeVp, false)
27 | this.renderContent(this.props)
28 |
29 | this.onKeyDown = this.onKeyDown.bind(this)
30 | this.onClose = this.onClose.bind(this)
31 | this.doClose = this.doClose.bind(this)
32 |
33 | window.addEventListener('keydown', this.onKeyDown)
34 | setTimeout(() => {
35 | if (this.props.onOpen) {
36 | this.props.onOpen()
37 | }
38 | this.setState({ active: true })
39 | }, 10)
40 | }
41 |
42 | componentWillUnmount() {
43 | document.body.className = document.body.className.replace(
44 | ' pl-modal-open',
45 | ''
46 | )
47 | document.body.removeEventListener('touchmove', freezeVp, false)
48 | window.removeEventListener('keydown', this.onKeyDown)
49 | ReactDOM.unmountComponentAtNode(this.portal)
50 | document.body.removeChild(this.portal)
51 | }
52 |
53 | render() {
54 | return null
55 | }
56 |
57 | componentWillReceiveProps(nextProps) {
58 | if (!this.props.shouldClose && nextProps.shouldClose) {
59 | this.doClose()
60 | }
61 | }
62 |
63 | componentDidUpdate() {
64 | this.renderContent(this.props)
65 | }
66 |
67 | renderContent() {
68 | var content = (
69 | <>
70 |
75 | this.onClose(e)}>
76 |
102 |
103 | >
104 | )
105 |
106 | ReactDOM.render(content, this.portal)
107 | }
108 |
109 | onClose(e) {
110 | if (
111 | this.props.onClose &&
112 | String(e.target.className).indexOf('pl-modal-cell') >= 0
113 | ) {
114 | this.doClose()
115 | }
116 | }
117 |
118 | doClose() {
119 | this.setState({ anim: 'is-leaving' })
120 | setTimeout(() => {
121 | this.setState({
122 | anim: `is-leaving is-${this.props.submitted ? 'submitted' : 'closed'}`
123 | })
124 | }, 10)
125 | this.onCloseTimeout = setTimeout(() => this.props.onClose(), 500)
126 | }
127 |
128 | onKeyDown(e) {
129 | if (e.keyCode === 27) {
130 | // Esc
131 | this.doClose()
132 | }
133 | if (e.keyCode === 13 && this.props.onPressEnter) {
134 | // Enter
135 | this.props.onPressEnter()
136 | }
137 | }
138 | }
139 |
140 | require('react-styl')(`
141 | .pl-modal-open
142 | overflow: hidden
143 | touch-action: none
144 | .pl-modal
145 | position: fixed
146 | z-index: 2000
147 | top: 0
148 | right: 0
149 | bottom: 0
150 | left: 0
151 | overflow-y: auto
152 | -webkit-transform: translate3d(0, 0, 0)
153 | .pl-modal-table
154 | display: table;
155 | table-layout: fixed;
156 | height: 100%;
157 | width: 100%;
158 | .pl-modal-cell
159 | display: table-cell;
160 | height: 100%;
161 | width: 100%;
162 | vertical-align: middle;
163 | padding: 50px;
164 | .pl-modal-content
165 | position: relative;
166 | overflow: hidden;
167 | border-radius: 2px;
168 | margin-left: auto;
169 | margin-right: auto;
170 | max-width: 520px;
171 | background-color: #fff
172 |
173 | .pl-modal-cell
174 | position: relative;
175 | -webkit-transition-property: opacity,-webkit-transform;
176 | transition-property: opacity,-webkit-transform;
177 | transition-property: opacity,transform;
178 | transition-property: opacity,transform,-webkit-transform
179 |
180 | .pl-modal-cell.is-entering>.pl-modal-content
181 | opacity: 0;
182 | -webkit-transform: translateY(50px) scale(.95);
183 | transform: translateY(50px) scale(.95)
184 |
185 | .pl-modal-cell.is-active.is-entering>.pl-modal-content
186 | opacity: 1;
187 | -webkit-transform: translateY(0) scale(1);
188 | transform: translateY(0) scale(1);
189 | -webkit-transition-timing-function: cubic-bezier(.15,1.45,.55,1);
190 | transition-timing-function: cubic-bezier(.15,1.45,.55,1);
191 | -webkit-transition-duration: .4s;
192 | transition-duration: .4s
193 |
194 | .pl-modal-cell.is-leaving.is-closed>.pl-modal-content
195 | opacity: 0;
196 | -webkit-transform: translateY(50px) scale(.95);
197 | transform: translateY(50px) scale(.95);
198 | -webkit-transition-timing-function: ease-in-out;
199 | transition-timing-function: ease-in-out;
200 | -webkit-transition-duration: .2s;
201 | transition-duration: .2s
202 |
203 | .pl-modal-cell.is-leaving.is-submitted>.pl-modal-content
204 | opacity: 0;
205 | -webkit-transform: translateY(-300px) translateZ(-70px) rotateX(10deg);
206 | transform: translateY(-300px) translateZ(-70px) rotateX(10deg);
207 | -webkit-transition-property: opacity,-webkit-transform;
208 | transition-property: opacity,-webkit-transform;
209 | transition-property: opacity,transform;
210 | transition-property: opacity,transform,-webkit-transform;
211 | -webkit-transition-timing-function: cubic-bezier(.5,-.33,1,1);
212 | transition-timing-function: cubic-bezier(.5,-.33,1,1);
213 | -webkit-transition-duration: .2s;
214 | transition-duration: .2s
215 |
216 | .pl-modal
217 | -webkit-transition-property: opacity;
218 | transition-property: opacity
219 |
220 | .pl-modal-bg
221 | position: fixed;
222 | top: 0;
223 | right: 0;
224 | bottom: 0;
225 | left: 0
226 | touch-action: none
227 |
228 | .pl-modal-bg
229 | background: rgba(0,0,0,.6);
230 | z-index: 1
231 |
232 | .pl-modal-bg.is-entering
233 | opacity: 0;
234 | -webkit-transition-duration: .2s;
235 | transition-duration: .2s;
236 | -webkit-transition-timing-function: ease;
237 | transition-timing-function: ease
238 |
239 | .pl-modal-bg.is-active.is-entering
240 | opacity: 1
241 |
242 | .pl-modal-bg.is-leaving
243 | opacity: 1;
244 | -webkit-transition-delay: .2s;
245 | transition-delay: .2s;
246 | -webkit-transition-duration: .2s;
247 | transition-duration: .2s;
248 | -webkit-transition-timing-function: ease-in-out;
249 | transition-timing-function: ease-in-out
250 |
251 | .pl-modal-bg.is-active.is-leaving
252 | opacity: 0
253 |
254 | .pl-modal-content
255 | border-radius: 6px;
256 | background-color: #f5f5f7;
257 | box-shadow: 0 12px 30px 0 rgba(0,0,0,.5),inset 0 1px 0 0 hsla(0,0%,100%,.65);
258 | -webkit-backface-visibility: hidden;
259 | backface-visibility: hidden
260 |
261 | `)
262 |
--------------------------------------------------------------------------------
/src/reducers/Identity.js:
--------------------------------------------------------------------------------
1 | import { IdentityConstants } from 'actions/Identity'
2 | import { NetworkConstants } from 'actions/Network'
3 |
4 | const officialIdentities =
5 | typeof OfficialIdentities !== 'undefined' ? OfficialIdentities : []
6 |
7 | const initialState = {
8 | contract: null,
9 | officialIdentities: [],
10 | identities: [],
11 | claimVerifiers: [],
12 | keys: [],
13 | claims: [],
14 | createIdentityResponse: undefined,
15 | createVerifierResponse: undefined,
16 | createKeyResponse: undefined,
17 | createClaimResponse: undefined,
18 | removeClaimResponse: undefined,
19 | checkClaimResponse: undefined,
20 | approveExecutionResponse: undefined,
21 | eventsResponse: undefined,
22 | lastDeployedIdentity: undefined,
23 | events: []
24 | }
25 |
26 | function getInitialState() {
27 | var state = {
28 | ...initialState
29 | }
30 | try {
31 | state.identities = JSON.parse(localStorage.identities)
32 | } catch (e) {
33 | /* Ignore */
34 | }
35 | try {
36 | state.claimVerifiers = JSON.parse(localStorage.claimVerifiers)
37 | } catch (e) {
38 | /* Ignore */
39 | }
40 | return applyContractNames(state)
41 | }
42 |
43 | function applyContractNames(state) {
44 | var names = {}
45 | state.identities.forEach(i => (names[i.address] = i.name))
46 | state.claimVerifiers.forEach(i => (names[i.address] = i.name))
47 | return { ...state, names }
48 | }
49 |
50 | export const Purposes = [
51 | { id: '1', name: 'Management' },
52 | { id: '2', name: 'Action' },
53 | { id: '3', name: 'Claim Signer' },
54 | { id: '4', name: 'Encryption' }
55 | ]
56 |
57 | export const KeyTypes = [{ id: '1', name: 'ECDSA' }, { id: '2', name: 'RSA' }]
58 |
59 | export default function Identity(state = getInitialState(), action = {}) {
60 | switch (action.type) {
61 | case NetworkConstants.CHANGE_SUCCESS:
62 | return {
63 | ...state,
64 | officialIdentities: officialIdentities[action.id] || []
65 | }
66 |
67 | case IdentityConstants.RESET:
68 | return getInitialState()
69 |
70 | case IdentityConstants.CHECK_CLAIM:
71 | return { ...state, checkClaimResponse: 'submitted' }
72 |
73 | case IdentityConstants.CHECK_CLAIM_HASH:
74 | return { ...state, checkClaimResponse: 'in-pool' }
75 |
76 | case IdentityConstants.CHECK_CLAIM_SUCCESS:
77 | return { ...state, checkClaimResponse: 'success' }
78 |
79 | case IdentityConstants.CHECK_CLAIM_ERROR:
80 | return { ...state, checkClaimResponse: 'error' }
81 |
82 | case IdentityConstants.ADD_CLAIM:
83 | return { ...state, addClaimResponse: 'submitted' }
84 |
85 | case IdentityConstants.ADD_CLAIM_HASH:
86 | return { ...state, addClaimResponse: 'in-pool' }
87 |
88 | case IdentityConstants.ADD_CLAIM_SUCCESS:
89 | return { ...state, addClaimResponse: 'success' }
90 |
91 | case IdentityConstants.ADD_CLAIM_ERROR:
92 | return { ...state, addClaimResponse: 'error' }
93 |
94 | case IdentityConstants.REMOVE_CLAIM:
95 | return { ...state, removeClaimResponse: 'submitted' }
96 |
97 | case IdentityConstants.REMOVE_CLAIM_HASH:
98 | return { ...state, removeClaimResponse: 'in-pool' }
99 |
100 | case IdentityConstants.REMOVE_CLAIM_SUCCESS:
101 | return { ...state, removeClaimResponse: 'success' }
102 |
103 | case IdentityConstants.REMOVE_CLAIM_ERROR:
104 | return { ...state, removeClaimResponse: 'error' }
105 |
106 | case IdentityConstants.ADD_KEY:
107 | return { ...state, addKeyResponse: 'submitted' }
108 |
109 | case IdentityConstants.ADD_KEY_HASH:
110 | return { ...state, addKeyResponse: 'in-pool' }
111 |
112 | case IdentityConstants.ADD_KEY_SUCCESS:
113 | return { ...state, addKeyResponse: 'success' }
114 |
115 | case IdentityConstants.ADD_KEY_ERROR:
116 | return { ...state, addKeyResponse: 'error' }
117 |
118 | case IdentityConstants.REMOVE_KEY:
119 | return { ...state, removeKeyResponse: 'submitted' }
120 |
121 | case IdentityConstants.REMOVE_KEY_HASH:
122 | return { ...state, removeKeyResponse: 'in-pool' }
123 |
124 | case IdentityConstants.REMOVE_KEY_SUCCESS:
125 | return { ...state, removeKeyResponse: 'success' }
126 |
127 | case IdentityConstants.REMOVE_KEY_ERROR:
128 | return { ...state, removeKeyResponse: 'error' }
129 |
130 | case IdentityConstants.APPROVE_EXECUTION:
131 | return { ...state, approveExecutionResponse: 'submitted' }
132 |
133 | case IdentityConstants.APPROVE_EXECUTION_HASH:
134 | return { ...state, approveExecutionResponse: 'in-pool' }
135 |
136 | case IdentityConstants.APPROVE_EXECUTION_SUCCESS:
137 | return { ...state, approveExecutionResponse: 'success' }
138 |
139 | case IdentityConstants.APPROVE_EXECUTION_ERROR:
140 | return { ...state, approveExecutionResponse: 'error' }
141 |
142 | case IdentityConstants.DEPLOY:
143 | return { ...state, createIdentityResponse: 'submitted' }
144 |
145 | case IdentityConstants.DEPLOY_HASH:
146 | return { ...state, createIdentityResponse: 'in-pool' }
147 |
148 | case IdentityConstants.DEPLOY_RECEIPT: {
149 | let newState = {
150 | ...state,
151 | createIdentityResponse: 'success',
152 | identities: [
153 | ...state.identities,
154 | {
155 | name: action.name,
156 | address: action.receipt.contractAddress,
157 | owner: action.owner,
158 | type: action.identityType,
159 | signerServices: action.signerServices
160 | }
161 | ],
162 | lastDeployedIdentity: action.receipt.contractAddress
163 | }
164 | localStorage.identities = JSON.stringify(newState.identities)
165 | return applyContractNames(newState)
166 | }
167 |
168 | case IdentityConstants.IMPORT: {
169 | let newState = {
170 | ...state,
171 | identities: [
172 | ...state.identities,
173 | {
174 | name: action.name,
175 | address: action.address,
176 | owner: action.owner,
177 | type: action.identityType
178 | }
179 | ]
180 | }
181 | localStorage.identities = JSON.stringify(newState.identities)
182 | return applyContractNames(newState)
183 | }
184 |
185 | case IdentityConstants.DEPLOY_VERIFIER:
186 | return { ...state, createVerifierResponse: 'submitted' }
187 |
188 | case IdentityConstants.DEPLOY_VERIFIER_HASH:
189 | return {
190 | ...state,
191 | contract: action.contract,
192 | createVerifierResponse: 'in-pool'
193 | }
194 |
195 | case IdentityConstants.DEPLOY_VERIFIER_RECEIPT: {
196 | let newState = {
197 | ...state,
198 | createVerifierResponse: 'success',
199 | claimVerifiers: [
200 | ...state.claimVerifiers,
201 | {
202 | name: action.name,
203 | address: action.receipt.contractAddress,
204 | owner: action.owner,
205 | trustedIdentity: action.trustedIdentity,
206 | methodName: action.methodName,
207 | claimType: action.claimType
208 | }
209 | ]
210 | }
211 | localStorage.claimVerifiers = JSON.stringify(newState.claimVerifiers)
212 | return applyContractNames(newState)
213 | }
214 |
215 | case IdentityConstants.GET_EVENTS:
216 | return { ...state, eventsResponse: null }
217 |
218 | case IdentityConstants.GET_EVENTS_SUCCESS:
219 | return { ...state, events: action.events, eventsResponse: 200 }
220 |
221 | case IdentityConstants.REMOVE: {
222 | let newState = {
223 | ...state,
224 | identities: state.identities.filter(i => i.address !== action.address)
225 | }
226 | localStorage.identities = JSON.stringify(newState.identities)
227 | return applyContractNames(newState)
228 | }
229 |
230 | case IdentityConstants.REMOVE_VERIFIER: {
231 | let newState = {
232 | ...state,
233 | claimVerifiers: state.claimVerifiers.filter(
234 | i => i.address !== action.address
235 | )
236 | }
237 | localStorage.claimVerifiers = JSON.stringify(newState.claimVerifiers)
238 | return applyContractNames(newState)
239 | }
240 | }
241 |
242 | return state
243 | }
244 |
--------------------------------------------------------------------------------
/src/contracts/ClaimVerifier.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "abi": [
3 | {
4 | "constant": false,
5 | "inputs": [
6 | {
7 | "name": "_identity",
8 | "type": "address"
9 | },
10 | {
11 | "name": "claimType",
12 | "type": "uint256"
13 | }
14 | ],
15 | "name": "checkClaim",
16 | "outputs": [
17 | {
18 | "name": "claimValid",
19 | "type": "bool"
20 | }
21 | ],
22 | "payable": false,
23 | "stateMutability": "nonpayable",
24 | "type": "function"
25 | },
26 | {
27 | "constant": true,
28 | "inputs": [],
29 | "name": "trustedClaimHolder",
30 | "outputs": [
31 | {
32 | "name": "",
33 | "type": "address"
34 | }
35 | ],
36 | "payable": false,
37 | "stateMutability": "view",
38 | "type": "function"
39 | },
40 | {
41 | "constant": true,
42 | "inputs": [
43 | {
44 | "name": "_identity",
45 | "type": "address"
46 | },
47 | {
48 | "name": "claimType",
49 | "type": "uint256"
50 | }
51 | ],
52 | "name": "claimIsValid",
53 | "outputs": [
54 | {
55 | "name": "claimValid",
56 | "type": "bool"
57 | }
58 | ],
59 | "payable": false,
60 | "stateMutability": "view",
61 | "type": "function"
62 | },
63 | {
64 | "constant": true,
65 | "inputs": [
66 | {
67 | "name": "sig",
68 | "type": "bytes"
69 | },
70 | {
71 | "name": "dataHash",
72 | "type": "bytes32"
73 | }
74 | ],
75 | "name": "getRecoveredAddress",
76 | "outputs": [
77 | {
78 | "name": "addr",
79 | "type": "address"
80 | }
81 | ],
82 | "payable": false,
83 | "stateMutability": "view",
84 | "type": "function"
85 | },
86 | {
87 | "inputs": [
88 | {
89 | "name": "_trustedClaimHolder",
90 | "type": "address"
91 | }
92 | ],
93 | "payable": false,
94 | "stateMutability": "nonpayable",
95 | "type": "constructor"
96 | },
97 | {
98 | "anonymous": false,
99 | "inputs": [
100 | {
101 | "indexed": false,
102 | "name": "_identity",
103 | "type": "address"
104 | },
105 | {
106 | "indexed": false,
107 | "name": "claimType",
108 | "type": "uint256"
109 | }
110 | ],
111 | "name": "ClaimValid",
112 | "type": "event"
113 | },
114 | {
115 | "anonymous": false,
116 | "inputs": [
117 | {
118 | "indexed": false,
119 | "name": "_identity",
120 | "type": "address"
121 | },
122 | {
123 | "indexed": false,
124 | "name": "claimType",
125 | "type": "uint256"
126 | }
127 | ],
128 | "name": "ClaimInvalid",
129 | "type": "event"
130 | }
131 | ],
132 | "data": "608060405234801561001057600080fd5b5060405160208061099183398101806040528101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505061090e806100836000396000f300608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630382ad261461006757806336938354146100cc5780639fecd55414610123578063c3b129e314610188575b600080fd5b34801561007357600080fd5b506100b2600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061023f565b604051808215151515815260200191505060405180910390f35b3480156100d857600080fd5b506100e161033a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561012f57600080fd5b5061016e600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061035f565b604051808215151515815260200191505060405180910390f35b34801561019457600080fd5b506101fd600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803560001916906020019092919050505061080a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600061024b838361035f565b156102c4577f5637aa6b77cde2de563765b75a65099af73d3bf22cb9d089a64a01777823208e8383604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a160019050610334565b7fc1e461cfcaa9ff5efbb053582a325fbfebec1d94ac7e9d9958ee7f74c2b6b5588383604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1600090505b92915050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008060008060608060008060008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168c604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140182815260200192505050604051809103902094508c73ffffffffffffffffffffffffffffffffffffffff1663c9100bcb866040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808260001916600019168152602001915050600060405180830381600087803b15801561046357600080fd5b505af1158015610477573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525060c08110156104a157600080fd5b8101908080519060200190929190805190602001909291908051906020019092919080516401000000008111156104d757600080fd5b828101905060208101848111156104ed57600080fd5b815185600182028301116401000000008211171561050a57600080fd5b5050929190602001805164010000000081111561052657600080fd5b8281019050602081018481111561053c57600080fd5b815185600182028301116401000000008211171561055957600080fd5b5050929190602001805164010000000081111561057557600080fd5b8281019050602081018481111561058b57600080fd5b81518560018202830111640100000000821117156105a857600080fd5b505092919050505050809a50819b50829c50839d50849e5050505050508c8c87604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140183815260200182805190602001908083835b602083101515610644578051825260208201915060208101905060208303925061061f565b6001836020036101000a0380198251168184511680821785525050505050509050019350505050604051809103902093508360405180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018260001916600019168152602001915050604051809103902092506106c7878461080a565b915081604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c01000000000000000000000000028152601401915050604051809103902090506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d202158d8260036040518363ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180836000191660001916815260200182815260200192505050602060405180830381600087803b1580156107bd57600080fd5b505af11580156107d1573d6000803e3d6000fd5b505050506040513d60208110156107e757600080fd5b81019080805190602001909291905050509a505050505050505050505092915050565b60008060008060006041875114151561082657600094506108d8565b6020870151935060408701519250606087015160001a9150601b8260ff16101561085157601b820191505b600186838686604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156108c8573d6000803e3d6000fd5b5050506020604051035190508094505b50505050929150505600a165627a7a7230582042ef457e2cd928a20c0b38f63a4fb625efecebd129c6bd8f1c040f7aea4a354b0029"
133 | }
--------------------------------------------------------------------------------