Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a potentially localStorage backed database #48

Merged
merged 4 commits into from
Oct 22, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ typings/

# webpack generated output
output/
.web3c/

# contract artifacts
test/build/*
4 changes: 2 additions & 2 deletions test/keymanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const keymanager = require('../web3c/keymanager');

describe('Key Manager', function() {
it('can encrypt and decrypt', async function() {
let km1 = new keymanager();
let km1 = new keymanager(null, false);
km1.getSecretKey();
let km2 = new keymanager();
let km2 = new keymanager(null, false);
km2.getSecretKey();

let pubkey = km2.getPublicKey();
Expand Down
2 changes: 1 addition & 1 deletion test/mockgateway/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function handleRequest (req) {
'result': []
};
// Arbitrarily chosen.
let manager = new keymanager();
let manager = new keymanager(null, false);
manager._db.setItem('me', JSON.stringify({
'secretKey': '0x263357bd55c11524811cccf8c9303e3298dd71abeb1b20f3ea7db07655dba9e9',
'publicKey': '0x59e35409ffdb0be6a74acc88d5e99e2b50782662fa5bf834b8b9d53bc59c7c4a'
Expand Down
9 changes: 9 additions & 0 deletions test/web3c.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ describe('Web3', () => {
assert.equal(64 + 2, key.key.length);
});

it('should support transient contracts with separate key state', async () => {
let firstContract = (new web3c(gw)).confidential.Contract(artifact.abi, undefined, {saveSession: false});
let secondContract = (new web3c(gw)).confidential.Contract(artifact.abi, undefined, {saveSession: false});

let firstKey = firstContract.currentProvider.keymanager.getPublicKey();
let secondKey = secondContract.currentProvider.keymanager.getPublicKey();
assert.notEqual(firstKey, secondKey);
});

it('should deploy a confidential counter contract', async () => {
let counterContract = (new web3c(gw)).confidential.Contract(artifact.abi);
try {
Expand Down
27 changes: 20 additions & 7 deletions web3c/confidential.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const KeyManager = require('./keymanager');
* web3.eth.Contract communication through a ConfidentialProvider.
*/
const Confidential = function (web3) {
this.keyManager = new KeyManager(web3);
let confidentialShim = ConfidentialProvider(this.keyManager, web3._requestManager);
this.keyManager = new KeyManager(web3, '.web3c');
let provider = new ConfidentialProvider(this.keyManager, web3._requestManager);
Confidential.methods(web3.extend).forEach((method) => {
method.setRequestManager(web3._requestManager);
method.attachToObject(this);
Expand All @@ -29,35 +29,48 @@ const Confidential = function (web3) {
*/
this.Contract = (abi, address, options) => {
let c = new web3.eth.Contract(abi, address, options);
c.setProvider(confidentialShim);
let instanceProvider = provider;

let keymanager = this.keyManager;
if (options && options.saveSession === false) {
keymanager = new KeyManager(web3, false);
willscott marked this conversation as resolved.
Show resolved Hide resolved
instanceProvider = new ConfidentialProvider(keymanager, web3._requestManager);
}

c.setProvider(instanceProvider);


let boundEvent = c._decodeEventABI;
c._decodeEventABI = function (data) {
if (data.logIndex == 0 && data.topics &&
data.topics[0] == '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') {
keymanager.add(data.address, data.data);
} else {
// decoding happens at request manager.
// decoding happens in the confidential provider.
}
return boundEvent.call(c, data);
};

// hook to ensure deployed contracts retain the confidential provider.
// Deployed contracts are instantiated with clone.
// This patch rebinds the confidential provider, which is otherwise lost.
let boundClone = c.clone.bind(c);
c.clone = () => {
let cloned = boundClone();
cloned.setProvider(confidentialShim);
cloned.setProvider(c.currentProvider);
cloned._decodeEventABI = c._decodeEventABI;
return cloned;
};

if (options && options.key) {
this.keyManager.add(address, options.key);
keymanager.add(address, options.key);
}

return c;
};

this.resetKeyManager = () => {
this.keyManager.reset();
};
};

function getPublicKeyOutputFormatter (t) {
Expand Down
92 changes: 46 additions & 46 deletions web3c/confidentialprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,56 @@ const CONFIDENTIAL_PREFIX = '00707269';
* ConfidentialProvider resolves calls from a Web3.eth.Contract, in particular
* `eth_sendTransaction` and `eth_call`, and transforms them into confidential
* transactions and calls.
* @param keymanager KeyManager The key manager holding the local keypair and
* tracking keypair information about the remote contract.
* @param internalManager web3.RequestManager The underlying manager for sending
* transactiosn to a web3 gateway.
*/
function ConfidentialProvider (keymanager, internalManager) {
let outstanding = [];
return {
send: ConfidentialProvider.send.bind({
keymanager: keymanager,
outstanding: outstanding,
manager: internalManager,
}),
sendBatch: ConfidentialProvider.sendBatch.bind({
keymanager: keymanager,
manager: internalManager,
}),
addSubscription: internalManager.addSubscription.bind(internalManager),
removeSubscription: internalManager.removeSubscription.bind(internalManager),
clearSubscriptions: internalManager.clearSubscriptions.bind(internalManager),
};
}

/**
* send will take calls from a web3 component, wrap them in a secure channel,
* and pass them to the underlying inernalManager.
* @param payload Object The web3 call payload.
* @param callback Function The function to call with the result.
*/
ConfidentialProvider.send = function confidentialSend (payload, callback) {
let transform = new ConfidentialSendTransform(this.manager.provider, this.keymanager);
class ConfidentialProvider {
/**
* Create a new confidential provider.
* @param {KeyManager} keymanager The key manager holding the local keypair and
* tracking keypair information about the remote contract.
* @param {web3.RequestManager} internalManager The underlying manager for sending
* transactiosn to a web3 gateway.
*/
constructor(keymanager, internalManager) {
this.outstanding = [];
this.keymanager = keymanager;
this.manager = internalManager;
this.addSubscription = internalManager.addSubscription.bind(internalManager);
this.removeSubscription = internalManager.removeSubscription.bind(internalManager);
this.clearSubscriptions = internalManager.clearSubscriptions.bind(internalManager);
}

if (payload.method === 'eth_sendTransaction') {
transform.ethSendTransaction(payload, callback, this.outstanding);
} else if (payload.method == 'eth_call') {
transform.ethCall(payload, callback);
} else if (payload.method == 'eth_getLogs') {
transform.ethLogs(payload, callback);
} else if (payload.method == 'eth_getTransactionReceipt') {
transform.ethTransactionReciept(payload, callback, this.outstanding);
} else {
const provider = this.manager.provider;
return provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, callback);
/**
* send will take calls from a web3 component, wrap them in a secure channel,
* and pass them to the underlying inernalManager.
* @param payload Object The web3 call payload.
* @param callback Function The function to call with the result.
*/
send(payload, callback) {
let transform = new ConfidentialSendTransform(this.manager.provider, this.keymanager);
if (payload.method === 'eth_sendTransaction') {
transform.ethSendTransaction(payload, callback, this.outstanding);
}
else if (payload.method == 'eth_call') {
transform.ethCall(payload, callback);
}
else if (payload.method == 'eth_getLogs') {
transform.ethLogs(payload, callback);
}
else if (payload.method == 'eth_getTransactionReceipt') {
transform.ethTransactionReciept(payload, callback, this.outstanding);
}
else {
const provider = this.manager.provider;
return provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, callback);
}
}
sendBatch(data, callback) {
// TODO
willscott marked this conversation as resolved.
Show resolved Hide resolved
return this.manager.sendBatch(data, callback);
}
};
}


ConfidentialProvider.sendBatch = function confidentialSendBatch (data, callback) {
// TODO
return this.manager.sendBatch(data, callback);
};

/**
* Wrap transactions in a confidential channel.
Expand Down
13 changes: 12 additions & 1 deletion web3c/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Confidential = require('./confidential');
let localWeb3 = undefined;

/**
* Web3c is a wrapper that can be invocated in the same way as Web3.
* Web3c is a wrapper that can be invoked in the same way as Web3.
* Expects Web3 v1.0
*/
module.exports = function (provider) {
Expand All @@ -16,15 +16,26 @@ module.exports = function (provider) {
}
};

// Option 1: At load time, the web3c webpack module finds an existing,
// compatible version of Web3 in the global namespace. We wrap the existing
// web3.
if (typeof Web3 !== 'undefined' && (new Web3()).version && !(new Web3()).version.api) {
localWeb3 = Web3;
// Option 2: At load time, the webpack module does not find web3.
// Require.ensure allows loading the bundled, compiled version of web3 as
// as a separate browser request, with the downside that it is asynchronous,
// and means that `new web3c()` will not be functional immediately.
} else if (typeof define !== 'undefined') {
// webpack
require.ensure(['web3'], function(require) {
localWeb3 = require('web3');
}, function(err) {
throw err;
}, 'web3');
// Option 3: Node or other uncompiled instantiations will directly require the
// web3 dependency. The `develblock` comment is removed by webpack, allowing
// its compiler to understand that the dependency is only loaded through a
// require.ensure and as such should be compiled into a separate module.
/* develblock:start */
} else {
localWeb3 = require('web3');
Expand Down
24 changes: 17 additions & 7 deletions web3c/keymanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ const LOCAL_KEY = 'me';
* Keymanager tracks contract keys. It also is responsible for refreshing
* short-term contract keys as needed through an underlying web3c interface.
* @param {Web3} web3 The wrapped web3 object for making gateway requests.
* @param {bool} useStorage Whether learned keys should be attempted to be stored.
* @param {String?} storage Where state should be attempted to be stored.
*/
class KeyManager {
constructor(web3, useStorage) {
if (useStorage && typeof localStorage !== 'undefined' && localStorage !== null) {
constructor(web3, storage) {
if (storage && typeof localStorage !== 'undefined' && localStorage !== null) {
willscott marked this conversation as resolved.
Show resolved Hide resolved
this._db = localStorage;
// Note: the dependency on node-localstorage below is removed from the
// webpack compiled version of the library, which will be running in a
// browser context.
/* develblock:start */
} else if (useStorage) {
} else if (storage) {
//TODO: allow setting storage location.
this._db = new require('node-localstorage').LocalStorage('.web3c');
this._db = new require('node-localstorage').LocalStorage(storage);
/* develblock:end */
} else {
this._db = new Map();
Expand All @@ -37,7 +40,7 @@ class KeyManager {
if (address == LOCAL_KEY) {
throw new Error('invalid contract address');
}
if (this._db.getItem(address) !== undefined &&
if (this._db.getItem(address) &&
JSON.parse(this._db.getItem(address)).longterm !== key) {
throw new Error('refusing to change longterm key for address');
}
Expand Down Expand Up @@ -78,6 +81,13 @@ class KeyManager {
this._web3.confidential.getPublicKey(address, this.onKey.bind(this, address, callback));
}

/**
* Reset state and flush keys.
*/
reset() {
this._db.clear();
}

/**
* Track short term keys in responses to requests made in `get`.
* @param {String} address EthHex the address of the contract
Expand Down Expand Up @@ -108,7 +118,7 @@ class KeyManager {
}

/**
* Get the lcoal keypair for the client.
* Get the local keypair for the client.
* @private
* @returns {Object} the local keypair.
*/
Expand Down