Ezerous
4 years ago
commit
7480151755
11 changed files with 1512 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||
|
# Set the default behavior, in case people don't have core.autocrlf set. |
||||
|
* text=auto eol=lf |
@ -0,0 +1,15 @@ |
|||||
|
# Node |
||||
|
/node_modules |
||||
|
|
||||
|
# IDE |
||||
|
.DS_Store |
||||
|
.idea |
||||
|
|
||||
|
# Logs |
||||
|
/log |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
|
||||
|
# npm |
||||
|
*.tgz |
@ -0,0 +1,9 @@ |
|||||
|
# Jetbrains |
||||
|
.idea |
||||
|
|
||||
|
# Git |
||||
|
.gitattributes |
||||
|
|
||||
|
# Package managers |
||||
|
yarn.lock |
||||
|
*.tgz |
@ -0,0 +1,21 @@ |
|||||
|
MIT License |
||||
|
|
||||
|
Copyright (c) 2020 Ezerous |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
@ -0,0 +1,7 @@ |
|||||
|
# @ezerous/eth-identity-provider |
||||
|
|
||||
|
[![Version](https://img.shields.io/npm/v/@ezerous/eth-identity-provider.svg)](https://www.npmjs.com/package/@ezerous/eth-identity-provider) |
||||
|
[![Dependencies](https://img.shields.io/david/Ezerous/eth-identity-provider.svg)](https://david-dm.org/Ezerous/eth-identity-provider) |
||||
|
[![License](https://img.shields.io/npm/l/@ezerous/eth-identity-provider.svg)](https://www.npmjs.com/package/@ezerous/eth-identity-provider) |
||||
|
|
||||
|
An Ethereum [OrbitDB identity provider](https://github.com/orbitdb/orbit-db-identity-provider). |
@ -0,0 +1,13 @@ |
|||||
|
{ |
||||
|
"name": "@ezerous/eth-identity-provider", |
||||
|
"version": "0.1.0", |
||||
|
"description": "An Ethereum orbit-db-identity-provider.", |
||||
|
"license": "MIT", |
||||
|
"author": "Ezerous <ezerous@gmail.com>", |
||||
|
"main": "src/index.js", |
||||
|
"repository": "github:Ezerous/breeze", |
||||
|
"dependencies": { |
||||
|
"level": "~6.0.1", |
||||
|
"orbit-db-identity-provider": "~0.3.1" |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
import EthereumIdentityProvider from "./ΕthereumIdentityProvider"; |
||||
|
import EthereumContractIdentityProvider from "./ΕthereumContractIdentityProvider"; |
||||
|
|
||||
|
export { EthereumIdentityProvider, EthereumContractIdentityProvider }; |
@ -0,0 +1,23 @@ |
|||||
|
import level from 'level'; |
||||
|
|
||||
|
/* Used in development only to store the identity.signatures.publicKey so developers don't have to |
||||
|
repeatedly sign theOrbitDB creation transaction in MetaMask when React development server reloads |
||||
|
the app */ |
||||
|
const concordiaDB = level('./concordia/identities'); |
||||
|
|
||||
|
async function storeIdentitySignaturePubKey(key, signaturePubKey) { |
||||
|
await concordiaDB.put(key, signaturePubKey); |
||||
|
} |
||||
|
|
||||
|
// If it exists, it returns the identity.signatures.publicKey for the given key (key is the
|
||||
|
// concatenation of identity.publicKey + identity.signatures.id)
|
||||
|
async function getIdentitySignaturePubKey(key) { |
||||
|
try { |
||||
|
return await concordiaDB.get(key); |
||||
|
} catch (err) { |
||||
|
if (err && err.notFound) return null; // Not found
|
||||
|
throw err; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export { storeIdentitySignaturePubKey, getIdentitySignaturePubKey }; |
@ -0,0 +1,131 @@ |
|||||
|
/* eslint-disable no-console */ |
||||
|
/* eslint-disable no-return-await */ |
||||
|
import IdentityProvider from 'orbit-db-identity-provider'; |
||||
|
import { getIdentitySignaturePubKey, storeIdentitySignaturePubKey } from './levelUtils'; |
||||
|
|
||||
|
const LOGGING_PREFIX = 'EthereumContractIdentityProvider: '; |
||||
|
|
||||
|
class EthereumContractIdentityProvider extends IdentityProvider { |
||||
|
constructor(options = {}) { |
||||
|
if (!EthereumContractIdentityProvider.web3) { |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because web3 wasn't set. ` |
||||
|
+ 'Please use setWeb3(web3) first!'); |
||||
|
} |
||||
|
|
||||
|
if (!EthereumContractIdentityProvider.contractAddress) { |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because contractAddress wasn't set. ` |
||||
|
+ 'Please use setContractAddress(contractAddress) first!'); |
||||
|
} |
||||
|
|
||||
|
super(options); |
||||
|
|
||||
|
// Optional (will be grabbed later if omitted)
|
||||
|
const { id } = options; |
||||
|
if (id) { |
||||
|
// Set Orbit's Identity Id (user's Ethereum address + contract's address)
|
||||
|
const { userAddress, contractAddress } = EthereumContractIdentityProvider.splitId(id); |
||||
|
if (EthereumContractIdentityProvider.web3.utils.isAddress(userAddress) |
||||
|
&& EthereumContractIdentityProvider.contractAddress === contractAddress) { |
||||
|
this.id = id; |
||||
|
this.userAddress = userAddress; |
||||
|
} |
||||
|
else |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because an invalid id was supplied.`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static get type() { return 'ethereum'; } |
||||
|
|
||||
|
async getId() { |
||||
|
// Id wasn't in the constructor, grab it now
|
||||
|
if (!this.id) { |
||||
|
const accounts = await EthereumContractIdentityProvider.web3.eth.getAccounts(); |
||||
|
if (!accounts[0]) { |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because no web3 accounts were found (
|
||||
|
locked Metamask?).`);
|
||||
|
} |
||||
|
[this.userAddress] = accounts; |
||||
|
this.id = this.userAddress + EthereumContractIdentityProvider.contractAddress; |
||||
|
} |
||||
|
return this.id; |
||||
|
} |
||||
|
|
||||
|
async signIdentity(data) { |
||||
|
if (process.env.NODE_ENV === 'development') { // Don't sign repeatedly while in development
|
||||
|
console.debug(`${LOGGING_PREFIX}Attempting to find stored Orbit identity data...`); |
||||
|
const signaturePubKey = await getIdentitySignaturePubKey(data); |
||||
|
if (signaturePubKey) { |
||||
|
const identity = { |
||||
|
userAddress: this.userAddress, |
||||
|
pubKeySignId: data, |
||||
|
signatures: { publicKey: signaturePubKey }, |
||||
|
}; |
||||
|
if (await EthereumContractIdentityProvider.verifyIdentity(identity)) { |
||||
|
console.debug(`${LOGGING_PREFIX}Found and verified stored Orbit identity data!`); |
||||
|
return signaturePubKey; |
||||
|
} |
||||
|
console.debug(`${LOGGING_PREFIX}Stored Orbit identity data couldn't be verified.`); |
||||
|
} else console.debug(`${LOGGING_PREFIX}No stored Orbit identity data were found.`); |
||||
|
} |
||||
|
return await this.doSignIdentity(data); |
||||
|
} |
||||
|
|
||||
|
// eslint-disable-next-line consistent-return
|
||||
|
async doSignIdentity(data) { |
||||
|
try { |
||||
|
const signaturePubKey = await EthereumContractIdentityProvider.web3.eth.personal.sign(data, this.userAddress, ''); |
||||
|
if (process.env.NODE_ENV === 'development') { |
||||
|
storeIdentitySignaturePubKey(data, signaturePubKey) |
||||
|
.then(() => { |
||||
|
console.debug(`${LOGGING_PREFIX}Successfully stored current Orbit identity data.`); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
console.warn(`${LOGGING_PREFIX}Couldn't store current Orbit identity data...`); |
||||
|
}); |
||||
|
} |
||||
|
return signaturePubKey; // Password not required for MetaMask
|
||||
|
} catch (error) { |
||||
|
if (error.code && error.code === 4001) { |
||||
|
console.debug(`${LOGGING_PREFIX}User denied message signature.`); |
||||
|
return await this.doSignIdentity(data); |
||||
|
} |
||||
|
|
||||
|
console.error(`${LOGGING_PREFIX}Failed to sign data.`); |
||||
|
console.error(error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static async verifyIdentity(identity) { |
||||
|
const pubKeySignId = identity.pubKeySignId ? identity.pubKeySignId : identity.publicKey + identity.signatures.id; |
||||
|
const { userAddress } = identity.userAddress ? identity: EthereumContractIdentityProvider.splitId(identity.id); |
||||
|
|
||||
|
// Verify that identity was signed by the ID
|
||||
|
return new Promise((resolve) => { |
||||
|
resolve(EthereumContractIdentityProvider.web3.eth.accounts.recover(pubKeySignId, |
||||
|
identity.signatures.publicKey) === userAddress); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Initialize by supplying a web3 object
|
||||
|
static setWeb3(web3) { |
||||
|
EthereumContractIdentityProvider.web3 = web3; |
||||
|
} |
||||
|
|
||||
|
// Initialize by supplying a contract's address (to be used as a point of reference)
|
||||
|
static setContractAddress(contractAddress) { |
||||
|
EthereumContractIdentityProvider.contractAddress = contractAddress; |
||||
|
} |
||||
|
|
||||
|
static splitId(id) { |
||||
|
const regex = /(0x.*)(0x.*)/g; |
||||
|
const match = regex.exec(id); |
||||
|
if (match && match.length === 3) |
||||
|
return { userAddress: match[1], contractAddress: match[2] }; |
||||
|
throw new Error(`${LOGGING_PREFIX}Invalid id ${id}! Couldn't split it to userAddress, contractAddress.`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
EthereumContractIdentityProvider.web3 = {}; |
||||
|
EthereumContractIdentityProvider.contractAddress = {}; |
||||
|
|
||||
|
export default EthereumContractIdentityProvider; |
@ -0,0 +1,107 @@ |
|||||
|
/* eslint-disable no-console */ |
||||
|
/* eslint-disable no-return-await */ |
||||
|
import IdentityProvider from 'orbit-db-identity-provider'; |
||||
|
import { getIdentitySignaturePubKey, storeIdentitySignaturePubKey } from './levelUtils'; |
||||
|
|
||||
|
const LOGGING_PREFIX = 'EthereumIdentityProvider: '; |
||||
|
|
||||
|
class EthereumIdentityProvider extends IdentityProvider { |
||||
|
constructor(options = {}) { |
||||
|
if (!EthereumIdentityProvider.web3) { |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because web3 wasn't set. ` |
||||
|
+ 'Please use setWeb3(web3) first!'); |
||||
|
} |
||||
|
|
||||
|
super(options); |
||||
|
|
||||
|
// Optional (will be grabbed later if omitted)
|
||||
|
const { id } = options; |
||||
|
if (id){ |
||||
|
// Set Orbit's Identity Id (user's Ethereum address)
|
||||
|
if (EthereumIdentityProvider.web3.utils.isAddress(id)) |
||||
|
this.id = id; |
||||
|
else |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because an invalid id was supplied.`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static get type() { return 'ethereum'; } |
||||
|
|
||||
|
async getId() { |
||||
|
// Id wasn't in the constructor, grab it now
|
||||
|
if (!this.id) { |
||||
|
const accounts = await EthereumIdentityProvider.web3.eth.getAccounts(); |
||||
|
if (!accounts[0]) { |
||||
|
throw new Error(`${LOGGING_PREFIX}Couldn't create identity, because no web3 accounts were found (
|
||||
|
locked Metamask?).`);
|
||||
|
} |
||||
|
|
||||
|
[this.id] = accounts; |
||||
|
} |
||||
|
return this.id; |
||||
|
} |
||||
|
|
||||
|
// Data to be signed is identity.publicKey + identity.signatures.id
|
||||
|
async signIdentity(data) { |
||||
|
if (process.env.NODE_ENV === 'development') { // Don't sign repeatedly while in development
|
||||
|
console.debug(`${LOGGING_PREFIX}Attempting to find stored Orbit identity data...`); |
||||
|
const signaturePubKey = await getIdentitySignaturePubKey(data); |
||||
|
if (signaturePubKey) { |
||||
|
const identity = { |
||||
|
id: this.id, |
||||
|
pubKeySignId: data, |
||||
|
signatures: { publicKey: signaturePubKey }, |
||||
|
}; |
||||
|
if (await EthereumIdentityProvider.verifyIdentity(identity)) { |
||||
|
console.debug(`${LOGGING_PREFIX}Found and verified stored Orbit identity data!`); |
||||
|
return signaturePubKey; |
||||
|
} |
||||
|
console.debug(`${LOGGING_PREFIX}Stored Orbit identity data couldn't be verified.`); |
||||
|
} else console.debug(`${LOGGING_PREFIX}No stored Orbit identity data were found.`); |
||||
|
} |
||||
|
return await this.doSignIdentity(data); |
||||
|
} |
||||
|
|
||||
|
// eslint-disable-next-line consistent-return
|
||||
|
async doSignIdentity(data) { |
||||
|
try { |
||||
|
const signaturePubKey = await EthereumIdentityProvider.web3.eth.personal.sign(data, this.id, ''); |
||||
|
if (process.env.NODE_ENV === 'development') { |
||||
|
storeIdentitySignaturePubKey(data, signaturePubKey) |
||||
|
.then(() => { |
||||
|
console.debug(`${LOGGING_PREFIX}Successfully stored current Orbit identity data.`); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
console.warn(`${LOGGING_PREFIX}Couldn't store current Orbit identity data...`); |
||||
|
}); |
||||
|
} |
||||
|
return signaturePubKey; // Password not required for MetaMask
|
||||
|
} catch (error) { |
||||
|
if (error.code && error.code === 4001) { |
||||
|
console.debug(`${LOGGING_PREFIX}User denied message signature.`); |
||||
|
return await this.doSignIdentity(data); |
||||
|
} |
||||
|
|
||||
|
console.error(`${LOGGING_PREFIX}Failed to sign data.`); |
||||
|
console.error(error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Verifies that identity was signed by the ID
|
||||
|
static async verifyIdentity(identity) { |
||||
|
const pubKeySignId = identity.pubKeySignId ? identity.pubKeySignId : identity.publicKey + identity.signatures.id; |
||||
|
return new Promise((resolve) => { |
||||
|
resolve(EthereumIdentityProvider.web3.eth.accounts.recover(pubKeySignId, |
||||
|
identity.signatures.publicKey) === identity.id); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Initialize by supplying a web3 object
|
||||
|
static setWeb3(web3) { |
||||
|
EthereumIdentityProvider.web3 = web3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
EthereumIdentityProvider.web3 = {}; |
||||
|
|
||||
|
export default EthereumIdentityProvider; |
File diff suppressed because it is too large
Loading…
Reference in new issue