-
Notifications
You must be signed in to change notification settings - Fork 1
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
Milestone 2 #77
base: master
Are you sure you want to change the base?
Milestone 2 #77
Conversation
macor161
commented
Nov 20, 2018
•
edited
Loading
edited
- File encryption
- Extract ObjectACL to its own repo
- Display gas usage in tests
- Bytecode size script
- AragonOS upgrade
- Javascript library documentation
- Functions to permanently delete files
Show gas usage when launching tests
Datastore acl extract
Security alerts
Pull Request Test Coverage Report for Build 116
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started reviewing this, will continue later as it's a huge PR ;-) but I want to let some comments so you can start reviewing them. As it happened before, I don't know your codebase as well as you, so feel free to correct me wherever I'm wrong, please ;-)
contracts/Datastore.sol
Outdated
|
||
modifier onlyFileOwner(uint256 _fileId) { | ||
require(permissions.isOwner(_fileId, msg.sender)); | ||
_; | ||
} | ||
|
||
function initialize(address _datastoreACL) onlyInit public | ||
{ | ||
function initialize(address _objectACL) onlyInit public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be a good idea to enforce ObjectACL
here instead of address
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup totally right! Done! :)
function getFileEncryptionKey(uint _fileId) external view returns(string) { | ||
if (hasReadAccess(_fileId, msg.sender)) { | ||
FileLibrary.File storage file = fileList.files[_fileId]; | ||
return file.cryptoKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this cryptoKey
is used. Is it the actual private key used to encrypt files? If so, anybody can see it as it's in the blockchain, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are totally right! :) In the next two milestones, we will store the encryption key using a privacy layer (keep, enigma or NuCypher) but in the meantime we left it there for the sake of convenience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Where are you now using that key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contracts/Datastore.sol
Outdated
function deleteFile(uint _fileId, bool _isDeleted, bool _deletePermanently) public onlyFileOwner(_fileId) { | ||
if (_isDeleted && _deletePermanently) { | ||
fileList.permanentlyDeleteFile(_fileId); | ||
emit DeleteFilePermanently(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these delete events include the fileId
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we started doing a clean up of our events (#83)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I don't think I get it. In that PR it seems you are even removing more fields, up to the point of leaving events empty. How are you planning to use the events. How will you know which objects the events are related to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes sorry Bigen I didn't explain myself very well. We started doing a big clean up of events because the majority of the parameters were not really useful and we didn't used them. So to optimize our bytecode size, we removed every params and a few events we thought were redundant. Now that the clean up is done in #83 we're supposed to start adding more params like fileId and groupId and more that we think can be useful for the users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, makes sense. I'll have another look at it when events are fulfilled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started adding event params in this pull request: #86 We are still debating on what to add next. Maybe for FileChange
and SettingsChange
we will add the type of change.
contracts/Datastore.sol
Outdated
function deleteFilesPermanently(uint256[] _fileIds) public { | ||
for(uint256 i = 0; i < _fileIds.length; i++) | ||
fileList.permanentlyDeleteFile(_fileIds[i]); | ||
emit DeleteFilePermanently(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case this event finally includes fileId
(see previous comment), then it should happen at per file level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See new commits cherry picked from #86
contracts/Datastore.sol
Outdated
require(hasWriteAccess(_fileId, msg.sender)); | ||
|
||
fileList.setEncryptionKey(_fileId, _cryptoKey); | ||
emit FileContentUpdate(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this event include the fileId
too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing as previous comment :)
contracts/Datastore.sol
Outdated
function setEncryptionProvider(EncryptionProvider _encryptionProvider) public { | ||
require(settings.encryptionProvider == EncryptionProvider.None); | ||
settings.encryptionProvider = _encryptionProvider; | ||
emit SettingsChanged(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to emit a more specific event, in order to know exactly which setting was changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we are thinking of adding a param that will represent the type of change but we are still not sure if it's a good trade-off regarding the additional bytecode.
contracts/Datastore.sol
Outdated
* @notice Change the encryption provider | ||
* @param _encryptionProvider Encryption provider | ||
*/ | ||
function setEncryptionProvider(EncryptionProvider _encryptionProvider) public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the provider is changed, what happens with all the already existing files? Are they going to be encrypted? Is it possible to have unencrypted and encrypted files in the datastore simultaneously?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes what we have in mind (at least in the first couple releases) is to lock the choice of encryption and of storage provider when they are initially selected. also it is possible to have encrypted and unencrypted files in the datastore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this isn't enforce yet, right? Where is this function being used?
Besides, the same comment could apply to setEncryptionKey
. What happens if the file was already encrypted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No you're right it is only enforced in the setSettings
function. I think setEncryptionProvider
and setStorageProvider
were temporarily added for some tests. We should remove them shortly. setEncryptionKey
can be called if we remove access to someone and need to re-encrypt the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright setEncryptionProvider
and setStorageProvider
are now removed: #91
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@macor161 could you cherry-pick that one here too? thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those functions are now also removed in this pull request.
contracts/Datastore.sol
Outdated
* Since switching between storage providers is not supported, | ||
* the method can only be called if storage isn't set or already IPFS | ||
* the method can only be called if storage isn't set or already IPFS. | ||
* Also sets AES as the encryption provider. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it choose a fixed encryption provider? Also, see previous comment about already existing files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a single encryption provider right now, supporting multiple modes and multiple key length. But maybe in the future we will support more providers. As for the encryption, you are totally right, this was a mistake to have the || settings.encryptionProvider == EncryptionProvider.Aes
part. hmm.. but then it would prevent setting the storage provider once the encryption provider is set. We must review this function next week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We reviewed the function and it would indeed make much more sense to have a parameter for the encryption provider: #92
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think eventually the storage provider and encryption provider parameters should be more general to be able to accommodate any type of provider, but as for now it's IPFS and AES I guess it's fine, and it can be addressed later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree. We started with the idea of having a general function that accepts a Settings
struct as parameter but it is not possible right now with Solidity 0.4.24. So we ended up with a function with all the parameters for every providers but it obviously dosn't scale.
contracts/Datastore.sol
Outdated
function createGroup(string _groupName) external auth(DATASTORE_MANAGER_ROLE) returns (uint) { | ||
uint id = groups.createGroup(_groupName); | ||
function createGroup(string _groupName) external { | ||
groups.createGroup(_groupName); | ||
emit GroupChange(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a more specific event for group creation would be better. Also, including the group id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes now that the cleanup is done for the events, we are considering adding a NewGroup event or maybe add a parameter to GroupChange that will specify the type of change
contracts/Datastore.sol
Outdated
fileList.files[_fileId].isPublic = _isPublic; | ||
fileList.setPublic(_fileId, _isPublic); | ||
|
||
if (!_isPublic || (_isPublic && keccak256(_encryptionKey) == keccak256(""))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I understand this correctly. What does exactly _isPublic
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public files will be unencrypted, and therefore readable by anyone inside or outside the DAO. Private files will be encrypted and only the addresses/groups specified will be able to access it's content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't understand the purpose of the condition yet.
- First part would mean that file is private. Then you are setting the encryption key. But if it was private I guess it already had a key. What happens then? Is the file going to be unencrypted with the old key and re-encrypted with the new one? Otherwise it could become unreadable, right?
- Second part holds if it's public and it has an empty encryption key, which sounds redundant: why should it have a key if it's unencrypted? Anyway, then you are setting the key. What for if it's public, i.e., unencrypted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes there seems to be something wrong with this line: if (!_isPublic || (_isPublic && keccak256(_encryptionKey) == keccak256(""))) {
. We should have more details monday after our team meeting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok after a review with the team, the condition was an attempt to verify that the user had the right permissions but it turns out we don't need it since there is already a onlyFileOwner
check. So we removed it in #93
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@macor161 cherry-pick this one here too, please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waiting for a unified PR with all changes before continuing review.
contracts/Datastore.sol
Outdated
@@ -55,29 +58,30 @@ contract Datastore is AragonApp { | |||
string protocol; | |||
} | |||
|
|||
/** | |||
* TODO: Use AesSettings inside Settings when aragon supports nested structs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean exactly? I guess you mean in the frontend, right? Meanwhile I guess you could comment this struct out, as you already have aesName
and aesLength
above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes if I remember well nested structs were not supported in the frontend. But we can definitely remove them in the meantime. Done! :)
contracts/Datastore.sol
Outdated
@@ -26,6 +25,7 @@ contract Datastore is AragonApp { | |||
event NewGroupPermissions(address indexed entity); | |||
event NewPermissions(address indexed entity); | |||
event DeleteFile(address indexed entity); | |||
event DeleteFilePermanently(address indexed entity); | |||
event SettingsChanged(address indexed entity); | |||
event GroupChange(address indexed entity); | |||
event EntityPermissionsRemoved(address indexed entity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you are always emitting msg.sender
as entity
for all these events (the name entity
sounds a little bit confusing to me then, but no big deal). I still think that adding the modified entity here (File, Group, ...) could be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments inline, but my main concern is with encryption key management: I'm aware that integration with other services like Keep or Enigma are pending, but I don't think meanwhile it makes any sense to store encryption key on-chain where anybody can access it.
Besides, how is the encryption of the file handled when the encryption key is set? Is it done in the frontend? Where? If there already was a previous key, is the content unencrypted with the old key and then encrypted with the new one? (sorry if this was already answered before)
contracts/libraries/FileLibrary.sol
Outdated
uint lastFileId; | ||
mapping (uint => FileLibrary.File) files; | ||
} | ||
|
||
|
||
function addFile(FileList storage _self, string _storageRef, string _name, uint _fileSize, bool _isPublic) internal returns (uint fileId) { | ||
function addFile(FileList storage _self, string _storageRef, string _name, uint128 _fileSize, bool _isPublic, string _encryptionKey) internal returns (uint fileId) { | ||
_self.lastFileId = _self.lastFileId.add(1); | ||
|
||
_self.files[_self.lastFileId] = FileLibrary.File({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tried it myself, but it seems it's more efficient to just set any non-zero variable one by one:
https://github.com/aragon/aragon-apps/pull/479/files#r231335088
(See line 403/519 if it doesn't show up or it appears hidden as outdated comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I didn't know that!
* Also represents the total number of files stored. | ||
*/ | ||
* Id of the last file added to the datastore. | ||
* Also represents the total number of files stored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this means the count starts at 1, not at 0, right? Just curious, are you using 0 to signal non-existing file or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0
will be used by the root folder when folders are enabled.
return _self.lastFileId; | ||
} | ||
|
||
function setFileName(FileList storage _self, uint _fileId, string _newName) internal { | ||
_self.files[_fileId].name = _newName; | ||
_self.files[_fileId].lastModification = now; | ||
_self.files[_fileId].lastModification = uint64(now); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's very unlikely that this overflows, but you may want to consider using this:
https://github.com/aragon/aragonOS/blob/dev/contracts/common/TimeHelpers.sol#L45
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great tip, thanks! However to save some gas, we have a new implementation where we are not storing the lastModification and fileName in the smart contract anymore. We store them using the storage provider (IPFS or Swarm).
@@ -25,13 +24,10 @@ library PermissionLibrary { | |||
bytes32 FILE_READ_ROLE; | |||
bytes32 FILE_WRITE_ROLE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these constants could be defined here instead of passing them on initialize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question! Usually ACL roles are defined in the main smart contract itself but these roles are strictly used with the ObjectACL, and only by the PermissionLibrary. I think it would indeed make more sense to have them in the library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! :) I've also moved DATASTORE_GROUP
to the GroupLibrary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Encryption is indeed done in javascript, here. If it is already encrypted, it uses the same key to re-encrypt new content. If a permission is changed for a user, let's say we remove access to someone, we generate a new key and re-encrypt the file so the user cannot use the old key even if they managed to intercept it in the frontend.
I agree that the key should not be stored in plain text in production. In fact, we are extracting all the encryption logic in another branch since encryption won't be enabled in the initial release. However we've already started the process and it would be pretty time consuming to cherry pick all those changes. Can we show you the final result next week?
* Also represents the total number of files stored. | ||
*/ | ||
* Id of the last file added to the datastore. | ||
* Also represents the total number of files stored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0
will be used by the root folder when folders are enabled.
contracts/libraries/FileLibrary.sol
Outdated
uint lastFileId; | ||
mapping (uint => FileLibrary.File) files; | ||
} | ||
|
||
|
||
function addFile(FileList storage _self, string _storageRef, string _name, uint _fileSize, bool _isPublic) internal returns (uint fileId) { | ||
function addFile(FileList storage _self, string _storageRef, string _name, uint128 _fileSize, bool _isPublic, string _encryptionKey) internal returns (uint fileId) { | ||
_self.lastFileId = _self.lastFileId.add(1); | ||
|
||
_self.files[_self.lastFileId] = FileLibrary.File({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I didn't know that!
Yes, sure. Actually I'm not sure it makes sense to add encryption until we can integrate with one of the expected services or find an alternative solution. So for me it's fine if you keep it in a separate branch until everything is ready, if that works for you too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me now, thanks.