├── tests ├── __init__.py ├── env_template.py ├── test_all.py ├── test_drop.py ├── test_delete.py ├── test_update.py ├── test_encryption.py ├── test_create.py ├── test_insert.py ├── test_link.py ├── test_sharing.py └── test_base.py ├── requirements.txt ├── .gitignore ├── tutorial ├── imgs │ ├── env.png │ ├── dapp_list.png │ ├── homebrew.png │ ├── releases.png │ ├── sign_scan.png │ ├── terminal.jpg │ ├── create_dapp.png │ ├── myDapp_menu.png │ ├── nounce_sign.png │ ├── private_key.gif │ ├── quickstart.png │ ├── use_case_1.png │ ├── use_case_2.png │ ├── use_case_3.png │ ├── windows_cmd.png │ ├── change_chain.png │ ├── env_template.png │ ├── install_brew.png │ ├── upload_chain.png │ ├── decrypt_request.png │ ├── install_complete.png │ ├── installer_macos.png │ ├── metamask_testnet.png │ ├── white_list_popup.png │ ├── brew_post_install.png │ ├── create_dapp_confirm.png │ ├── python_install_path.png │ ├── request_publickey.png │ └── python_install_complete.png ├── README.md ├── Configure_Wallet.md └── Configure_Python.md ├── mindlakesdk ├── README.md ├── settings.py ├── LICENSE ├── datalake.py ├── utils.py ├── __init__.py ├── keyhelper.py ├── permission.py ├── message.py └── cryptor.py ├── examples ├── env_template.py ├── quickstart.py ├── use_case_2.py ├── use_case_1.py └── use_case_3.py ├── pyproject.toml ├── LICENSE └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | eth_account 2 | web3 3 | pynacl 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | .DS_Store 3 | *.pyc 4 | env.py 5 | keys.db 6 | __pycache__ 7 | -------------------------------------------------------------------------------- /tutorial/imgs/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/env.png -------------------------------------------------------------------------------- /tutorial/imgs/dapp_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/dapp_list.png -------------------------------------------------------------------------------- /tutorial/imgs/homebrew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/homebrew.png -------------------------------------------------------------------------------- /tutorial/imgs/releases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/releases.png -------------------------------------------------------------------------------- /tutorial/imgs/sign_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/sign_scan.png -------------------------------------------------------------------------------- /tutorial/imgs/terminal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/terminal.jpg -------------------------------------------------------------------------------- /tutorial/imgs/create_dapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/create_dapp.png -------------------------------------------------------------------------------- /tutorial/imgs/myDapp_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/myDapp_menu.png -------------------------------------------------------------------------------- /tutorial/imgs/nounce_sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/nounce_sign.png -------------------------------------------------------------------------------- /tutorial/imgs/private_key.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/private_key.gif -------------------------------------------------------------------------------- /tutorial/imgs/quickstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/quickstart.png -------------------------------------------------------------------------------- /tutorial/imgs/use_case_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/use_case_1.png -------------------------------------------------------------------------------- /tutorial/imgs/use_case_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/use_case_2.png -------------------------------------------------------------------------------- /tutorial/imgs/use_case_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/use_case_3.png -------------------------------------------------------------------------------- /tutorial/imgs/windows_cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/windows_cmd.png -------------------------------------------------------------------------------- /tutorial/imgs/change_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/change_chain.png -------------------------------------------------------------------------------- /tutorial/imgs/env_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/env_template.png -------------------------------------------------------------------------------- /tutorial/imgs/install_brew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/install_brew.png -------------------------------------------------------------------------------- /tutorial/imgs/upload_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/upload_chain.png -------------------------------------------------------------------------------- /tutorial/imgs/decrypt_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/decrypt_request.png -------------------------------------------------------------------------------- /tutorial/imgs/install_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/install_complete.png -------------------------------------------------------------------------------- /tutorial/imgs/installer_macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/installer_macos.png -------------------------------------------------------------------------------- /tutorial/imgs/metamask_testnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/metamask_testnet.png -------------------------------------------------------------------------------- /tutorial/imgs/white_list_popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/white_list_popup.png -------------------------------------------------------------------------------- /mindlakesdk/README.md: -------------------------------------------------------------------------------- 1 | Python SDK for Mind Network: https://mindnetwork.xyz 2 | Documentation: https://mind-network.gitbook.io/mind-lake-sdk/ -------------------------------------------------------------------------------- /tutorial/imgs/brew_post_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/brew_post_install.png -------------------------------------------------------------------------------- /tutorial/imgs/create_dapp_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/create_dapp_confirm.png -------------------------------------------------------------------------------- /tutorial/imgs/python_install_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/python_install_path.png -------------------------------------------------------------------------------- /tutorial/imgs/request_publickey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/request_publickey.png -------------------------------------------------------------------------------- /mindlakesdk/settings.py: -------------------------------------------------------------------------------- 1 | GATEWAY = 'https://sdk.mindnetwork.xyz/node' 2 | VERSION = 'v1.0.8' 3 | 4 | DEFAULT_CHAINID = '5' 5 | CLERK_CHAINID = '0' -------------------------------------------------------------------------------- /tutorial/imgs/python_install_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-python/HEAD/tutorial/imgs/python_install_complete.png -------------------------------------------------------------------------------- /examples/env_template.py: -------------------------------------------------------------------------------- 1 | walletAddressAlice = '' 2 | walletPrivateKeyAlice = '' 3 | 4 | walletAddressBob = '' 5 | walletPrivateKeyBob = '' 6 | 7 | walletAddressCharlie = '' 8 | walletPrivateKeyCharlie = '' 9 | 10 | appKey = '' 11 | -------------------------------------------------------------------------------- /tests/env_template.py: -------------------------------------------------------------------------------- 1 | walletAddress = '' 2 | walletPrivateKey = '' 3 | 4 | walletAddressAlice = '' 5 | walletPrivateKeyAlice = '' 6 | 7 | walletAddressBob = '' 8 | walletPrivateKeyBob = '' 9 | 10 | walletAddressCharlie = '' 11 | walletPrivateKeyCharlie = '' 12 | 13 | appKey = '' 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "mindlakesdk" 7 | version = "v1.0.8" 8 | authors = [ 9 | { name="Mind Labs", email="biz@mindnetwork.xyz" }, 10 | ] 11 | description = "A Python SDK to connect to Mind Lake" 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | ] 19 | keywords = ["web3", "encryption", "datalake"] 20 | dependencies = [ 21 | "eth_account", 22 | "web3", 23 | "pynacl" 24 | ] 25 | 26 | [project.urls] 27 | "Homepage" = "https://github.com/mind-network/mind-lake-sdk-python" 28 | "Bug Tracker" = "https://github.com/mind-network/mind-lake-sdk-python/issues" 29 | 30 | [tool.hatch.build] 31 | exclude = ["/examples", "/tests", "/tutorial"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The Python Packaging Authority 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 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 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | 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 THESOFTWARE. -------------------------------------------------------------------------------- /mindlakesdk/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The Python Packaging Authority 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 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 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | 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 THESOFTWARE. -------------------------------------------------------------------------------- /examples/quickstart.py: -------------------------------------------------------------------------------- 1 | import env 2 | import mindlakesdk 3 | import logging 4 | 5 | # logging.basicConfig(level=logging.DEBUG) 6 | 7 | # 1. connect to MindLake 8 | mindLake = mindlakesdk.connect(env.walletPrivateKeyAlice, env.appKey, chainID='5611') 9 | assert mindLake, mindLake.message 10 | 11 | result = mindLake.datalake.dropTable('test_table_enc') 12 | 13 | # 2. create a table 14 | result = mindLake.datalake.createTable('test_table_enc', 15 | [ 16 | mindLake.datalake.Column('id', mindLake.DataType.int4, False), 17 | mindLake.datalake.Column('token', mindLake.DataType.text, True) 18 | ]) 19 | assert result, result.message 20 | 21 | # 3. encrypt data 22 | result = mindLake.cryptor.encrypt('USDT','test_table_enc.token') 23 | assert result, result.message 24 | encryptedTokenName = result.data 25 | 26 | # 4. insert encrypted data 27 | result = mindLake.datalake.query(f"""INSERT INTO test_table_enc (id, token) 28 | VALUES (1, '{encryptedTokenName}')""") 29 | assert result, result.message 30 | 31 | # 5. query encrypted data 32 | result = mindLake.datalake.query("SELECT token FROM test_table_enc") 33 | assert result, result.message 34 | print(result.data['columnList'][0]) 35 | for row in result.data['data']: 36 | result = mindLake.cryptor.decrypt(row[0]) 37 | assert result, result.message 38 | print(result.data) 39 | -------------------------------------------------------------------------------- /tests/test_all.py: -------------------------------------------------------------------------------- 1 | import test_base 2 | import env 3 | import test_create 4 | import test_delete 5 | import test_drop 6 | import test_encryption 7 | import test_insert 8 | import test_link 9 | import test_sharing 10 | import test_update 11 | 12 | def test_all(walletPrivateKeyAlice, walletPrivateKeyBob, walletAddressAlice, walletAddressBob, appKey, GATEWAY): 13 | test_create.cases(walletPrivateKeyAlice, appKey, GATEWAY) 14 | test_delete.cases(walletPrivateKeyAlice, appKey, GATEWAY) 15 | test_drop.cases(walletPrivateKeyAlice, appKey, GATEWAY) 16 | test_encryption.cases(walletPrivateKeyAlice, appKey, GATEWAY) 17 | test_insert.cases(walletPrivateKeyAlice, appKey, GATEWAY) 18 | test_link.cases(walletPrivateKeyAlice, walletPrivateKeyBob, walletAddressAlice, walletAddressBob, appKey, GATEWAY) 19 | test_sharing.cases(walletPrivateKeyAlice, walletPrivateKeyBob, walletAddressAlice, walletAddressBob, appKey, GATEWAY) 20 | test_update.cases(walletPrivateKeyAlice, appKey, GATEWAY) 21 | 22 | if __name__ == '__main__': 23 | if hasattr(env, 'GATEWAY') and env.GATEWAY: 24 | test_all(env.walletPrivateKeyAlice, env.walletPrivateKeyBob, env.walletAddressAlice, env.walletAddressBob, env.appKey, env.GATEWAY) 25 | else: 26 | test_all(env.walletPrivateKeyAlice, env.walletPrivateKeyBob, env.walletAddressAlice, env.walletAddressBob, env.appKey, None) 27 | -------------------------------------------------------------------------------- /examples/use_case_2.py: -------------------------------------------------------------------------------- 1 | import env 2 | import mindlakesdk 3 | import requests 4 | from base64 import a85decode, a85encode 5 | from hashlib import md5 6 | 7 | # 1. connect to MindLake 8 | mindlake = mindlakesdk.connect(env.walletPrivateKeyAlice, env.appKey, chainID='5611') 9 | assert mindlake, mindlake.message 10 | 11 | result = mindlake.datalake.dropTable('album') 12 | 13 | # 2. create a table 14 | result = mindlake.datalake.createTable('album', 15 | [ 16 | mindlake.datalake.Column('name', mindlake.DataType.text, False), 17 | mindlake.datalake.Column('picture', mindlake.DataType.text, True), 18 | ]) 19 | assert result, result.message 20 | 21 | # 3. get a picture from github 22 | response = requests.get('https://avatars.githubusercontent.com/u/97393721') 23 | if response and response.status_code == 200: 24 | pic = response.content 25 | else: 26 | raise Exception('Failed to get picture from github') 27 | 28 | file = open('pic_origin.png', 'wb') 29 | file.write(pic) 30 | file.close() 31 | print('MD5 of the original picture pic_origin.png: ' + md5(pic).hexdigest()) 32 | 33 | # 4. encrypt and insert the data 34 | result = mindlake.cryptor.encrypt(a85encode(pic).decode(),'album.picture') 35 | assert result, result.message 36 | encryptedPic = result.data 37 | 38 | result = mindlake.datalake.query(f""" 39 | INSERT INTO "album" 40 | ("name", "picture") 41 | VALUES ('{'mind.png'}', 42 | '{encryptedPic}') returning *""") 43 | assert result, result.message 44 | 45 | # 5. query encrypted data 46 | result = mindlake.datalake.query('SELECT * FROM "album"') 47 | assert result, result.message 48 | 49 | # 6. decrypt the data 50 | for row in result.data['data']: 51 | name = row[0] 52 | resultPic = mindlake.cryptor.decrypt(row[1]) 53 | assert resultPic, resultPic.message 54 | decryptedPic = a85decode(resultPic.data) 55 | file = open(f'{name}', 'wb') 56 | file.write(decryptedPic) 57 | file.close() 58 | print(f'MD5 of the decrypted picture {name}: ' + md5(decryptedPic).hexdigest()) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mind Lake Python SDK 2 | 3 | An Python implementation for Mind Lake 4 | 5 | ## Description 6 | 7 | The Mind Lake SDK utilizes Mind Lake's encryption storage and privacy computing capabilities to provide secure data management. 8 | * Mind Lake is the backbone of Mind Network. 9 | * All data is end-to-end encrypted at the client-side SDK side, ensuring that plaintext data never leaves the user's client. 10 | * Cryptographic principles ensure that only the data owner can access their own plaintext data. 11 | * Additionally, Mind Lake's powerful privacy computing capabilities enable the performance of calculations and querying of encrypted data. 12 | 13 | ## Getting Started 14 | 15 | ### Dependencies 16 | 17 | * Python > 3.8 18 | * pip 19 | * web3 20 | * pynacl 21 | 22 | ### Installing 23 | 24 | * pip install mindlakesdk 25 | 26 | ### Import 27 | ``` 28 | import mindlakesdk 29 | ... 30 | ``` 31 | 32 | ### More examples 33 | * [step-by-step tutorial](/tutorial/README.md) 34 | * [quick starts](https://mind-network.gitbook.io/mind-lake-sdk/get-started) 35 | * [more examples](https://mind-network.gitbook.io/mind-lake-sdk/use-cases) 36 | 37 | 38 | 39 | ## code 40 | ``` 41 | mind-lake-sdk-python 42 | |-- mindlakesdk # source code 43 | | |-- __init__.py 44 | | |-- datalake.py 45 | | |-- permission.py 46 | | |-- cryptor.py 47 | | └-- utils.py 48 | |-- tests # unit test code 49 | |-- examples # use case examples 50 | |-- tutorial # step-by-step tutorial 51 | |-- README.md 52 | └--- LICENSE 53 | 54 | ``` 55 | 56 | ## Help 57 | 58 | Full doc: [https://mind-network.gitbook.io/mind-lake-sdk](https://mind-network.gitbook.io/mind-lake-sdk) 59 | 60 | ## Authors 61 | 62 | * Dennis [@NuIlPtr](https://twitter.com/nuilptr) 63 | * George [@georgemindnet](https://twitter.com/georgemindnet) 64 | 65 | ## Version History 66 | 67 | * v1.0 68 | * Initial Release 69 | * v1.0.1 70 | * Fix bug 71 | * v1.0.2 72 | * Improve performances 73 | * v1.0.5 74 | * Keep up the version number with TypeScript SDK 75 | * v1.0.6 76 | * Add support for Mind DataPack 77 | * v1.0.7 78 | * Update the connect function 79 | * v1.0.8 80 | * Add support for multiple chains 81 | 82 | ## License 83 | 84 | This project is licensed under the [MIT] License - see the LICENSE.md file for details 85 | -------------------------------------------------------------------------------- /mindlakesdk/datalake.py: -------------------------------------------------------------------------------- 1 | from mindlakesdk.utils import ResultType, Session, DataType 2 | import mindlakesdk.message 3 | import logging 4 | 5 | class DataLake: 6 | def __init__(self, session: Session): 7 | self.__session = session 8 | 9 | class Column(dict): 10 | def __init__(self, columnName: str, dataType: DataType, encrypt: bool): 11 | self.columnName = columnName 12 | self.type = dataType 13 | self.encrypt = encrypt 14 | 15 | def toDict(self): 16 | return { 17 | 'columnName': self.columnName, 18 | 'type': self.type.value, 19 | 'encrypt': self.encrypt 20 | } 21 | 22 | def createTable(self, tableName: str, columns: list, primaryKey: list = None) -> ResultType: 23 | columnsDict = [] 24 | for column in columns: 25 | columnsDict.append(column.toDict()) 26 | return mindlakesdk.message.sendCreateTable(self.__session, tableName, columnsDict, primaryKey) 27 | 28 | def listCocoon(self) -> ResultType: 29 | logging.debug("listCocoon") 30 | return mindlakesdk.message.sendListCocoon(self.__session) 31 | 32 | def linkTableToCocoon(self, tableName: str, cocoonName: str) -> ResultType: 33 | return mindlakesdk.message.sendLinkTableToCocoon(self.__session, tableName, cocoonName) 34 | 35 | def listTablesByCocoon(self, cocoonName: str) -> ResultType: 36 | return mindlakesdk.message.sendListTablesByCocoon(self.__session, cocoonName) 37 | 38 | def createCocoon(self, cocoonName: str) -> ResultType: 39 | return mindlakesdk.message.sendCreateCocoon(self.__session, cocoonName) 40 | 41 | def query(self, executeSql: str) -> ResultType: 42 | return mindlakesdk.message.sendQuery(self.__session, executeSql) 43 | 44 | def dropCocoon(self, cocoonName: str) -> ResultType: 45 | return mindlakesdk.message.sendDropCocoon(self.__session, cocoonName) 46 | 47 | def dropTable(self, tableName: str) -> ResultType: 48 | return mindlakesdk.message.sendDropTable(self.__session, tableName) 49 | 50 | def queryForDataAndMeta(self, executeSql: str) -> ResultType: 51 | return mindlakesdk.message.sendQueryForDataAndMeta(self.__session, executeSql) 52 | -------------------------------------------------------------------------------- /tests/test_drop.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | #test_base.drop_all_cocoon_and_table(env.walletPrivateKey) 5 | 6 | import logging 7 | setLogging(logging.INFO) 8 | #setLogging(logging.DEBUG) 9 | 10 | import env 11 | # logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 12 | 13 | import mindlakesdk 14 | print("============= start test ============") 15 | 16 | 17 | 18 | def prepare_test(mindlake: MindLake): 19 | tableName = 'test_table_nonencrypted' 20 | test_base.drop_test_table(mindlake, tableName) 21 | test_base.create_test_table_nonencrypted(mindlake, tableName) 22 | test_base.insert_test_table_nonencrypted(mindlake, tableName) 23 | 24 | cocoonName = 'test_cocoon_create' 25 | test_base.create_test_cocoon(mindlake, cocoonName) 26 | test_base.link_table_to_cocoon_test(mindlake, tableName, cocoonName) 27 | 28 | # by now, I should have at least one table and one cocoon, and table link to cocoon 29 | print("============= test preparation completed ============") 30 | 31 | def test_drop_cocoon_nonempty(mindlake: MindLake): 32 | print_test_functions 33 | 34 | prepare_test(mindlake) 35 | cocoonName = 'test_cocoon_create' 36 | code, data = test_base.drop_test_cocoon(mindlake, cocoonName) 37 | print(code, data) 38 | assert code == 40013, 'non-empty cocoon can not be dropped !' 39 | 40 | def test_drop_table(mindlake: MindLake): 41 | print_test_functions 42 | 43 | prepare_test(mindlake) 44 | tableName = 'test_table_nonencrypted' 45 | code, data = test_base.drop_test_table(mindlake, tableName) 46 | assert code == 0 and data == True, 'table has problems to drop !' 47 | 48 | def test_drop_cocoon(mindlake: MindLake): 49 | print_test_functions 50 | 51 | cocoonName = 'test_cocoon_create' 52 | code, data= test_base.drop_test_cocoon(mindlake, cocoonName) 53 | assert code == 0 and data == True, 'empty cocoon has problems to drop !' 54 | 55 | 56 | print("============= complete test ============") 57 | def cases(walletPrivateKey, appKey, GATEWAY): 58 | logging.info("==== start test | %s ===="%(__file__)) 59 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 60 | assert mindlake, 'mindlakesdk.connect failed !' 61 | test_base.drop_all_cocoon_and_table(mindlake) 62 | test_drop_cocoon_nonempty(mindlake) 63 | test_drop_table(mindlake) 64 | test_drop_cocoon(mindlake) 65 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 66 | 67 | if __name__ == "__main__": 68 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) 69 | -------------------------------------------------------------------------------- /examples/use_case_1.py: -------------------------------------------------------------------------------- 1 | import env 2 | import mindlakesdk 3 | 4 | # 1. connect to mindlake 5 | mindlake = mindlakesdk.connect(env.walletPrivateKeyAlice, env.appKey, chainID='5611') 6 | assert mindlake, mindlake.message 7 | 8 | result = mindlake.datalake.dropTable('wallet_balance') 9 | 10 | # 2. create a table 11 | result = mindlake.datalake.createTable('wallet_balance', 12 | [ 13 | mindlake.datalake.Column('WalletAddress', mindlake.DataType.text, False), 14 | mindlake.datalake.Column('Name', mindlake.DataType.text, True), 15 | mindlake.datalake.Column('Balance', mindlake.DataType.float4, True) 16 | ], 17 | primaryKey=['WalletAddress']) 18 | assert result, result.message 19 | 20 | # 3. encrypt and insert the data 21 | result = mindlake.cryptor.encrypt('Alice','wallet_balance.Name') 22 | assert result, result.message 23 | encryptedName = result.data 24 | result = mindlake.cryptor.encrypt(10.5,'wallet_balance.Balance') 25 | assert result, result.message 26 | encryptedBalance = result.data 27 | 28 | result = mindlake.datalake.query(f""" 29 | INSERT INTO "wallet_balance" 30 | ("WalletAddress", "Name", "Balance") 31 | VALUES ('{'0xB2F588A50E43f58FEb0c05ff86a30D0d0b1BF065'}', 32 | '{encryptedName}', 33 | '{encryptedBalance}') returning *""") 34 | assert result, result.message 35 | 36 | # 4. a compact way to encrypt and insert 37 | result = mindlake.datalake.query(f""" 38 | INSERT INTO "wallet_balance" ("WalletAddress", "Name", "Balance") 39 | VALUES ('0x420c08373E2ba9C7566Ba0D210fB42A20a1eD2f8', 40 | '{mindlake.cryptor.encrypt('Bob','wallet_balance.Name').data}', 41 | '{mindlake.cryptor.encrypt(20.5,'wallet_balance.Balance').data}') returning * 42 | """) 43 | assert result, result.message 44 | 45 | # 5. query all the encrypted data 46 | result = mindlake.datalake.query('SELECT * FROM "wallet_balance"') 47 | assert result, result.message 48 | 49 | # 6. print encryption data from query result 50 | print('The data stored in Mind Lake:') 51 | print('-'*77) 52 | print('|', result.data["columnList"][0], " "*28, '|', result.data["columnList"][1], 53 | " "*8, '|', result.data["columnList"][2], " "*5, '|') 54 | print('-'*77) 55 | for row in result.data['data']: 56 | print('|', row[0], '|', row[1][:10]+'...', '|', row[2][:10]+'...', '|') 57 | print('-'*77) 58 | 59 | # 7. query encrypted data with condition and decrypt the data 60 | # Note: the condition must be encrypted 61 | result = mindlake.datalake.query( 62 | f'''SELECT * FROM "wallet_balance" WHERE "Balance" > \ 63 | '{mindlake.cryptor.encrypt(15.0, mindlake.DataType.float4).data}' ''') 64 | assert result, result.message 65 | 66 | print() 67 | print('The data after decryption:') 68 | print('-'*66) 69 | print('|', result.data["columnList"][0], " "*28, '|', result.data["columnList"][1], 70 | " "*3, '|', result.data["columnList"][2], '|') 71 | print('-'*66) 72 | for row in result.data['data']: 73 | walletAddress = row[0] 74 | resultName = mindlake.cryptor.decrypt(row[1]) 75 | resultBalance = mindlake.cryptor.decrypt(row[2]) 76 | assert resultName, resultName.message 77 | assert resultBalance, resultBalance.message 78 | print('|', walletAddress, '|', resultName.data, '\t|', resultBalance.data, '\t |') 79 | print('-'*66) 80 | -------------------------------------------------------------------------------- /tests/test_delete.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | 5 | import logging 6 | setLogging(logging.INFO) 7 | # setLogging(logging.DEBUG) 8 | 9 | import env 10 | # logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 11 | 12 | import mindlakesdk 13 | print("============= start test ============") 14 | 15 | 16 | 17 | def test_delete_nonencrypted(mindlake: MindLake): 18 | print_test_functions() 19 | tableName = 'test_table_nonencrypted' 20 | test_base.drop_test_table(mindlake, tableName) 21 | test_base.create_test_table_nonencrypted(mindlake, tableName) 22 | count_select_all = test_base.insert_test_table_nonencrypted(mindlake, tableName) 23 | assert count_select_all == 2, "nonencrypted table insert wrong !" 24 | 25 | sql = "DELETE FROM %s WHERE mid = '%s'"%(tableName, 'a1') 26 | print(sql) 27 | q = mindlake.datalake.query(sql) 28 | print('Code: ', q.code, q.message, 'INSERT') 29 | logging.debug(q.data) 30 | assert q.code == 0, 'delete can not work' 31 | 32 | q = mindlake.datalake.query(f"SELECT * FROM {tableName}") 33 | print('Code: ', q.code, q.message, 'SELECT') 34 | logging.debug(q.data) 35 | count_select_all = len(q.data['data']) 36 | assert count_select_all == 0, 'delete does not work in nonencrypted table !' 37 | 38 | def test_delete_encrypted(mindlake: MindLake): 39 | print_test_functions() 40 | tableName = 'test_table_encrypted' 41 | test_base.drop_test_table(mindlake, tableName) 42 | test_base.create_test_table_encrypted(mindlake, tableName) 43 | data = { 44 | 'int4': 123, 45 | 'int8': 1234567890, 46 | 'float4': 1.2345678901234567890, 47 | 'float8': 1.2345678901234567890, 48 | 'decimal': 12345678901234567890, 49 | 'text': 'Hello', 50 | 'timestamp': datetime.datetime.now() 51 | } 52 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 53 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 54 | 55 | queryResult = mindlake.datalake.query(f"SELECT * FROM {tableName}") 56 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 57 | logging.debug(queryResult.data) 58 | count_select_all = len(queryResult.data['data']) 59 | assert queryResult.code == 0 and count_select_all == 2, 'decryption test failed !' 60 | 61 | q = mindlake.cryptor.encrypt(123, mindlake.DataType.int4) 62 | sql = f"""DELETE FROM "{tableName}" WHERE dataint4 = '{q.data}'""" 63 | q = mindlake.datalake.query(sql) 64 | print('Code: ', q.code, q.message, 'DELETE') 65 | logging.debug(q.data) 66 | assert q.code == 0, 'delete does not work in encrypted table !' 67 | 68 | q = mindlake.datalake.query(f"SELECT * FROM {tableName}") 69 | print('Code: ', q.code, q.message, 'SELECT') 70 | assert q, 'select failed !' 71 | logging.debug(q.data) 72 | count_select_all = len(q.data['data']) 73 | assert count_select_all == 0, 'delete does not work in encrypted table !' 74 | 75 | 76 | print("============= complete test ============") 77 | def cases(walletPrivateKey, appKey, GATEWAY): 78 | logging.info("==== start test | %s ===="%(__file__)) 79 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 80 | assert mindlake, 'mindlakesdk.connect failed !' 81 | test_base.drop_all_cocoon_and_table(mindlake) 82 | test_delete_nonencrypted(mindlake) 83 | test_delete_encrypted(mindlake) 84 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 85 | 86 | if __name__ == "__main__": 87 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) 88 | -------------------------------------------------------------------------------- /tests/test_update.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | 5 | import logging 6 | setLogging(logging.INFO) 7 | # setLogging(logging.DEBUG) 8 | 9 | import env 10 | # logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 11 | 12 | import mindlakesdk 13 | print("============= start test ============") 14 | 15 | 16 | def test_update_nonencrypted(mindlake: MindLake): 17 | print_test_functions() 18 | 19 | tableName = 'test_table_nonencrypted' 20 | test_base.drop_test_table(mindlake, tableName) 21 | test_base.create_test_table_nonencrypted(mindlake, tableName) 22 | count_select_all = test_base.insert_test_table_nonencrypted(mindlake, tableName) 23 | assert count_select_all == 2, "nonencrypted table insert wrong !" 24 | 25 | data = test_base.update_test_table_nonencrypted(mindlake, tableName) 26 | assert data[0][1] == 'b11', "nonencrypted table update wrong !" 27 | 28 | def test_update_encrypted(mindlake: MindLake): 29 | print_test_functions() 30 | 31 | tableName = 'test_table_encrypted' 32 | test_base.drop_test_table(mindlake, tableName) 33 | test_base.create_test_table_encrypted(mindlake, tableName) 34 | data = { 35 | 'int4': 123, 36 | 'int8': 1234567890, 37 | 'float4': 1.2345678901234567890, 38 | 'float8': 1.2345678901234567890, 39 | 'decimal': 12345678901234567890, 40 | 'text': 'Hello', 41 | 'timestamp': datetime.datetime.now() 42 | } 43 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 44 | 45 | data2 = { 46 | 'int4': 123, 47 | 'int8': 5234567890, 48 | 'float4': 5.2345678901234567890, 49 | 'float8': 5.2345678901234567890, 50 | 'decimal': 52345678901234567890, 51 | 'text': 'world', 52 | 'timestamp': datetime.datetime.now() 53 | } 54 | update_test_table_encrypted(mindlake, tableName, data2, data2['int4']) 55 | 56 | print("===== select * =====") 57 | queryResult = mindlake.datalake.query(f'SELECT * FROM "{tableName}"') 58 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 59 | logging.debug(queryResult.data) 60 | count_select_all = len(queryResult.data['data']) 61 | assert queryResult and count_select_all == 1, 'decryption test failed !' 62 | row = queryResult.data['data'][0] 63 | columnList = queryResult.data['columnList'] 64 | for i, cell in enumerate(row): 65 | print(f"===== decrypt column num {i} {columnList[i]}=====") 66 | q = mindlake.cryptor.decrypt(cell) 67 | print('Code: ', q.code, q.message, 'DECRYPT ', columnList[i]) 68 | print('Decrypted: Column', i, columnList[i], q.data) 69 | print('Origin:', data2[columnList[i][4:]]) 70 | if columnList[i] == 'datafloat4' or columnList[i] == 'datafloat8': 71 | assert q and abs(q.data - data2[columnList[i][4:]]) < 0.01, 'decryption test failed !' 72 | else: 73 | assert q and q.data == data2[columnList[i][4:]], 'decryption test failed !' 74 | 75 | 76 | print("============= complete test ============") 77 | def cases(walletPrivateKey, appKey, GATEWAY): 78 | logging.info("==== start test | %s ===="%(__file__)) 79 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 80 | assert mindlake, 'mindlakesdk.connect failed !' 81 | test_base.drop_all_cocoon_and_table(mindlake) 82 | test_update_nonencrypted(mindlake) 83 | test_update_encrypted(mindlake) 84 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 85 | 86 | if __name__ == "__main__": 87 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) 88 | 89 | -------------------------------------------------------------------------------- /examples/use_case_3.py: -------------------------------------------------------------------------------- 1 | import env 2 | import mindlakesdk 3 | import logging 4 | 5 | # logging.basicConfig(level=logging.DEBUG) 6 | 7 | # the policy grant target should be an existing user in MindLake, so first register Charlie 8 | mindlakeCharlie = mindlakesdk.connect(env.walletPrivateKeyCharlie, env.appKey, chainID='5611') 9 | assert mindlakeCharlie, mindlakeCharlie.message 10 | 11 | def writeDataGrantToCharlie(walletPrivateKey, appKey, data) -> str: 12 | # connect to MindLake 13 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, chainID='5611') 14 | assert mindlake, mindlake.message 15 | 16 | result = mindlake.datalake.dropTable('transaction_temp') 17 | 18 | # create a table 19 | result = mindlake.datalake.createTable('transaction_temp', 20 | [ 21 | mindlake.datalake.Column('WalletAddress', mindlake.DataType.text, True), 22 | mindlake.datalake.Column('Token', mindlake.DataType.text, True), 23 | mindlake.datalake.Column('Volume', mindlake.DataType.float4, True) 24 | ]) 25 | assert result, result.message 26 | 27 | # encrypt and insert 28 | for row in data: 29 | result = mindlake.datalake.query(f""" 30 | INSERT INTO "transaction_temp" ("WalletAddress", "Token", "Volume") 31 | VALUES ( 32 | '{mindlake.cryptor.encrypt(row["wallet"],'transaction_temp.WalletAddress').data}', 33 | '{mindlake.cryptor.encrypt(row["token"],'transaction_temp.Token').data}', 34 | '{mindlake.cryptor.encrypt(row["volume"],'transaction_temp.Volume').data}') 35 | """) 36 | assert result, result.message 37 | 38 | result = mindlake.permission.grant(env.walletAddressCharlie, 39 | ['transaction_temp.WalletAddress', 'transaction_temp.Token', 'transaction_temp.Volume']) 40 | assert result, result.message 41 | policyID = result.data 42 | return policyID 43 | 44 | # Alice write data to table and grant permission to Charlie 45 | policyIDAlice = writeDataGrantToCharlie(env.walletPrivateKeyAlice, env.appKey, [ 46 | {'wallet': '0x8CFB38b2cba74757431B205612E349B8b9a9E661', 'token': 'USDT', 'volume': 5.6}, 47 | {'wallet': '0xD862D48f36ce6298eFD00474eC852b8838a54F66', 'token': 'BUSD', 'volume': 6.3}, 48 | {'wallet': '0x8CFB38b2cba74757431B205612E349B8b9a9E661', 'token': 'BUSD', 'volume': 10.3} 49 | ]) 50 | 51 | # Bob write data to table and grant permission to Charlie 52 | policyIDBob = writeDataGrantToCharlie(env.walletPrivateKeyBob, env.appKey, [ 53 | {'wallet': '0xD862D48f36ce6298eFD00474eC852b8838a54F66', 'token': 'USDT', 'volume': 3.3}, 54 | {'wallet': '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', 'token': 'BUSD', 'volume': 9.8}, 55 | {'wallet': '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', 'token': 'USDT', 'volume': 7.7} 56 | ]) 57 | 58 | # Charlie confirm the permission 59 | result = mindlakeCharlie.permission.confirm(policyIDAlice) 60 | assert result, result.message 61 | result = mindlakeCharlie.permission.confirm(policyIDBob) 62 | assert result, result.message 63 | 64 | # Charlie query and calculate the total volume of each wallet 65 | result = mindlakeCharlie.datalake.query(f''' 66 | SELECT combine."WalletAddress", SUM(combine."Volume") FROM 67 | (SELECT "WalletAddress","Volume" FROM "{mindlakeCharlie.getNameSpace(env.walletAddressAlice)}"."transaction_temp" 68 | UNION ALL 69 | SELECT "WalletAddress","Volume" FROM "{mindlakeCharlie.getNameSpace(env.walletAddressBob)}"."transaction_temp") as combine 70 | GROUP BY "WalletAddress" 71 | ''') 72 | assert result, result.message 73 | 74 | print('-'*57) 75 | print('|', result.data["columnList"][0], " "*28, '|', result.data["columnList"][1], '\t|') 76 | print('-'*57) 77 | for row in result.data['data']: 78 | result = mindlakeCharlie.cryptor.decrypt(row[0]) 79 | assert result, result.message 80 | walletAddress = result.data 81 | result = mindlakeCharlie.cryptor.decrypt(row[1]) 82 | assert result, result.message 83 | sumVolume = result.data 84 | print(f'| {walletAddress} | {sumVolume:.1f}\t|') 85 | print('-'*57) 86 | -------------------------------------------------------------------------------- /tests/test_encryption.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | # test_base.drop_all_cocoon_and_table(env.walletPrivateKey) 5 | 6 | import logging 7 | setLogging(logging.INFO) 8 | # setLogging(logging.DEBUG) 9 | 10 | import env 11 | # logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 12 | 13 | import mindlakesdk 14 | print("============= start test ============") 15 | 16 | def prepare_test(mindlake: MindLake): 17 | tableName = 'test_table_encryption' 18 | test_base.drop_test_table(mindlake, tableName) 19 | test_base.create_test_table_encrypted(mindlake, tableName) 20 | 21 | # by now, I should have at least one table and one cocoon, and table link to cocoon 22 | print("============= test preparation completed ============") 23 | 24 | 25 | def test_insert_encrypted_data(mindlake: MindLake, data): 26 | print_test_functions() 27 | tableName = 'test_table_encryption' 28 | q = test_base.insert_test_table_encrypted(mindlake, tableName, data) 29 | assert q.code == 0, 'encryption test failed !' 30 | logging.debug(q.data) 31 | 32 | def test_query_decrypt(mindlake: MindLake, data): 33 | print_test_functions() 34 | tableName = 'test_table_encryption' 35 | 36 | print("===== select * =====") 37 | queryResult = mindlake.datalake.query(f'SELECT * FROM "{tableName}"') 38 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 39 | logging.debug(queryResult.data) 40 | count_select_all = len(queryResult.data['data']) 41 | assert queryResult and count_select_all == 1, 'decryption test failed !' 42 | row = queryResult.data['data'][0] 43 | columnList = queryResult.data['columnList'] 44 | for i, cell in enumerate(row): 45 | print(f"===== decrypt column num {i} {columnList[i]}=====") 46 | q = mindlake.cryptor.decrypt(cell) 47 | print('Code: ', q.code, q.message, 'DECRYPT ', columnList[i]) 48 | print('Decrypted: Column', i, columnList[i], q.data) 49 | print('Origin:', data[columnList[i][4:]]) 50 | if columnList[i] == 'datafloat4' or columnList[i] == 'datafloat8': 51 | assert q and abs(q.data - data[columnList[i][4:]]) < 0.01, 'decryption test failed !' 52 | else: 53 | assert q and q.data == data[columnList[i][4:]], 'decryption test failed !' 54 | 55 | def test_constant_encrypt_decrypt(mindlake: MindLake, data): 56 | print_test_functions() 57 | encryptedData = {} 58 | for k, v in data.items(): 59 | print(f"===== encrypt constant {k} =====") 60 | q = mindlake.cryptor.encrypt(v, mindlake.DataType[k]) 61 | print('Code: ', q.code, q.message, f'encrypt {k}') 62 | assert q and isinstance(q.data, str) and q.data[:2] == '\\x', 'encryption test failed !' 63 | encryptedData[k] = q.data 64 | 65 | logging.debug(encryptedData) 66 | 67 | for k, v in encryptedData.items(): 68 | print(f"===== decrypt contant {k} =====") 69 | q = mindlake.cryptor.decrypt(v) 70 | print('Code: ', q.code, q.message, f'decrypt {k}') 71 | print(k, q.data) 72 | if k == mindlake.DataType.float4.name or k == mindlake.DataType.float8.name: 73 | assert q and abs(q.data - data[k]) < 0.01, 'decryption test failed !' 74 | else: 75 | assert q and q.data == data[k], 'decryption test failed !' 76 | 77 | def cases(walletPrivateKey, appKey, GATEWAY): 78 | logging.info("==== start test | %s ===="%(__file__)) 79 | data = { 80 | 'int4': 123, 81 | 'int8': 1234567890, 82 | 'float4': 1.2345678901234567890, 83 | 'float8': 1.2345678901234567890, 84 | 'decimal': 12345678901234567890, 85 | 'text': 'Hello', 86 | 'timestamp': datetime.datetime.now() 87 | } 88 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 89 | assert mindlake, mindlake.message 90 | test_base.drop_all_cocoon_and_table(mindlake) 91 | prepare_test(mindlake) 92 | test_insert_encrypted_data(mindlake, data) 93 | test_query_decrypt(mindlake, data) 94 | test_constant_encrypt_decrypt(mindlake, data) 95 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 96 | 97 | if __name__ == "__main__": 98 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) 99 | cases(env.walletPrivateKeyAlice, env.appKey, env.GATEWAY) 100 | cases(env.walletPrivateKeyBob, env.appKey, env.GATEWAY) 101 | cases(env.walletPrivateKeyCharlie, env.appKey, env.GATEWAY) 102 | -------------------------------------------------------------------------------- /mindlakesdk/utils.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES, PKCS1_OAEP 2 | from Crypto.Util.Padding import pad, unpad 3 | from Crypto.Signature import pss 4 | from Crypto.PublicKey import RSA 5 | from Crypto.Hash import SHA256, HMAC 6 | from Crypto.Random import get_random_bytes 7 | from enum import Enum 8 | import json 9 | import mindlakesdk.settings as settings 10 | import logging 11 | 12 | class ResultType: 13 | def __init__(self, code: int, message: str = None, data = None): 14 | self.code = code 15 | self.message = message 16 | self.data = data 17 | 18 | def __bool__(self): 19 | return self.code == 0 20 | 21 | class DataType(Enum): 22 | int4 = 1 23 | int8 = 2 24 | float4 = 3 25 | float8 = 4 26 | decimal = 5 27 | text = 6 28 | timestamp = 7 29 | 30 | class Session: 31 | def __init__(self) -> None: 32 | self.walletAddress = None 33 | self.chainID = None 34 | self.isRegistered = False 35 | self.mk = None 36 | self.sk = None 37 | self.isLogin = False 38 | self.token = None 39 | self.accountID = None 40 | self.nodePK = None 41 | self.pkID = None 42 | self.appKey = None 43 | self.gateway = None 44 | self.requstSession = None 45 | 46 | class BlockChain: 47 | def __init__(self, chainObj: dict) -> None: 48 | self.chainID = chainObj["chainId"] 49 | self.rpcNode = chainObj["rpcNodeUrl"] 50 | self.contract = chainObj["smartAddress"] 51 | self.abi = chainObj["abi"] 52 | 53 | def getNameSpace(self, walletAddress: str): 54 | if self.value > 0: 55 | return f'evm{self.value}_{walletAddress}' 56 | 57 | def genRSAKey(): 58 | rsaKey = RSA.generate(2048) 59 | return rsaKey 60 | 61 | def genAESKey(): 62 | aesKey = get_random_bytes(16) 63 | return aesKey 64 | 65 | def sha256Hash(data): 66 | h = SHA256.new(data) 67 | return h.digest() 68 | 69 | def aesEncrypt(key, iv, data): 70 | cipher = AES.new(key, AES.MODE_CBC, iv) 71 | padded_data = pad(data, AES.block_size) 72 | encrypted_data = cipher.encrypt(padded_data) 73 | return encrypted_data 74 | 75 | def aesDecrypt(key, iv, data): 76 | cipher = AES.new(key, AES.MODE_CBC, iv) 77 | decrypted_data = cipher.decrypt(data) 78 | result = unpad(decrypted_data, AES.block_size) 79 | return result 80 | 81 | def aesGCMEncrypt(key, iv, data): 82 | cipher = AES.new(key, AES.MODE_GCM, iv) 83 | encrypted_data, tag = cipher.encrypt_and_digest(data) 84 | return tag + encrypted_data 85 | 86 | def aesGCMDecrypt(key, iv, data, tag): 87 | cipher = AES.new(key, AES.MODE_GCM, iv) 88 | decrypted_data = cipher.decrypt_and_verify(data, tag) 89 | return decrypted_data 90 | 91 | def hmacHash(key, data): 92 | h = HMAC.new(key, digestmod=SHA256) 93 | h.update(data) 94 | return h.digest() 95 | 96 | def rsaEncrypt(pubKey, data): 97 | public_key = RSA.import_key(pubKey) 98 | cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256) 99 | encrypted_data = cipher.encrypt(data) 100 | return encrypted_data 101 | 102 | def rsaDecrypt(priKey, data): 103 | private_key = RSA.import_key(priKey) 104 | cipher = PKCS1_OAEP.new(private_key, hashAlgo=SHA256) 105 | decrypted_data = cipher.decrypt(data) 106 | return decrypted_data 107 | 108 | def rsaSign(priKey: RSA.RsaKey, data: bytes) -> bytes: 109 | # private_key = RSA.import_key(priKey) 110 | h = SHA256.new(data) 111 | signature = pss.new(priKey).sign(h) 112 | return signature 113 | 114 | def rsaVerify(pubKey, data, signature): 115 | public_key = RSA.import_key(pubKey) 116 | h = SHA256.new(data) 117 | verifier = pss.new(public_key) 118 | try: 119 | verifier.verify(h, signature) 120 | return True 121 | except (ValueError, TypeError): 122 | return False 123 | 124 | def request(data, session: Session): 125 | headers = {} 126 | headers['Content-Type'] = 'application/json' 127 | headers['wa'] = session.walletAddress 128 | headers['chain'] = session.chainID 129 | headers['ver'] = settings.VERSION 130 | headers['app'] = session.appKey 131 | if session.token: 132 | headers['token'] = session.token 133 | response = session.requstSession.post(session.gateway, json=data, headers=headers) 134 | logging.debug("============== Mind SDK request ==============") 135 | logging.debug('MindSDKHeaders: %s'%headers) 136 | logging.debug("MindSDKData: %s"%data) 137 | logging.debug('MindSDKRequest: %s'%response.request.body.decode('utf-8')) 138 | logging.debug('MindSDKResponse: %s'%response.text) 139 | if response and response.status_code == 200: 140 | return json.loads(response.text) 141 | else: 142 | return None -------------------------------------------------------------------------------- /tests/test_create.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | #test_base.drop_all_cocoon_and_table(env.walletPrivateKey) 5 | 6 | import logging 7 | setLogging(logging.INFO) 8 | setLogging(logging.DEBUG) 9 | #setLogging(logging.NOTSET) 10 | 11 | 12 | import env 13 | #logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 14 | 15 | import mindlakesdk 16 | 17 | # def test_create_table_with_wrong_table_definition(walletPrivateKey, appKey): 18 | # print_test_functions() 19 | # #print(inspect.stack()[1][3]) 20 | # mindlake = mindlakesdk.connect(walletPrivateKey, appKey, env.GATEWAY) 21 | # test_base.drop_all_cocoon_and_table(mindlake, appKey) 22 | 23 | # tableName = "test_table_with_nocolumns" 24 | # columns = [] 25 | # result = mindlake.datalake.createTable(tableName, columns) 26 | # logging.info('createTable: %s %s %s'%(tableName, result.code, result.message)) 27 | # assert result.code == 40008, 'can not create table with no columns !' 28 | 29 | def test_table_create_insert_nonencrypted(walletPrivateKey, appKey, GATEWAY): 30 | print_test_functions() 31 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 32 | assert mindlake, 'mindlakesdk.connect failed !' 33 | test_base.drop_all_cocoon_and_table(mindlake) 34 | 35 | tableName = 'test_table_nonencrypted' 36 | test_base.drop_test_table(mindlake, tableName) 37 | code1 = test_base.create_test_table_nonencrypted(mindlake, tableName) 38 | code2 = test_base.create_test_table_nonencrypted(mindlake, tableName) 39 | #print(code1, code2) 40 | assert code1 == 0 and code2 == 40008, 'can not create duplicated table with same name 1!' 41 | 42 | def test_table_create_insert_encrypted(walletPrivateKey, appKey, GATEWAY): 43 | print_test_functions() 44 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 45 | assert mindlake, 'mindlakesdk.connect failed !' 46 | test_base.drop_all_cocoon_and_table(mindlake) 47 | 48 | tableName = 'test_table_encrypted' 49 | test_base.drop_test_table(mindlake, tableName) 50 | code1 = test_base.create_test_table_encrypted(mindlake, tableName) 51 | code2 = test_base.create_test_table_encrypted(mindlake, tableName) 52 | assert code1 == 0 and code2 == 40008, 'can not create duplicated table with same name 2!' 53 | 54 | def test_cocoon_create_tablelink(walletPrivateKey, appKey, GATEWAY): 55 | print_test_functions() 56 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 57 | assert mindlake, 'mindlakesdk.connect failed !' 58 | test_base.drop_all_cocoon_and_table(mindlake) 59 | 60 | cocoonName1 = 'test_cocoon_1' 61 | test_base.create_test_cocoon(mindlake, cocoonName1) 62 | cocoonName2 = 'test_cocoon_2' 63 | test_base.create_test_cocoon(mindlake, cocoonName2) 64 | 65 | result = mindlake.datalake.listCocoon() 66 | logging.info('listCocoon: %s %s'%(result.code, result.message)) 67 | logging.debug(result.data) 68 | assert check_coocoon_exists(result.data, cocoonName1) and check_coocoon_exists(result.data, cocoonName2), 'failed to create cocoon !' 69 | 70 | tableName1 = 'test_table_nonencrypted' 71 | test_base.create_test_table_nonencrypted(mindlake, tableName1) 72 | test_base.link_table_to_cocoon_test(mindlake, tableName1, cocoonName1) 73 | tableName2 = 'test_table_encrypted' 74 | test_base.create_test_table_encrypted(mindlake, tableName2) 75 | test_base.link_table_to_cocoon_test(mindlake, tableName2, cocoonName1) 76 | 77 | result = mindlake.datalake.listTablesByCocoon(cocoonName1) 78 | logging.info('listTablesByCocoon: %s %s %s'%(cocoonName1, result.code, result.message)) 79 | tables = result.data 80 | logging.debug(tables) 81 | assert check_table_exists(tables, tableName1) and check_table_exists(tables, tableName2), 'failed to link table to cocoon1 !' 82 | 83 | test_base.link_table_to_cocoon_test(mindlake, tableName2, cocoonName2) 84 | 85 | result = mindlake.datalake.listTablesByCocoon(cocoonName2) 86 | logging.info('listTablesByCocoon: %s %s %s'%(cocoonName2, result.code, result.message)) 87 | tables = result.data 88 | logging.debug(tables) 89 | assert not check_table_exists(tables, tableName1) and check_table_exists(tables, tableName2), 'failed to link table2 to cocoon2 !' 90 | 91 | 92 | def cases(walletPrivateKey, appKey, GATEWAY): 93 | logging.info("==== start test | %s ===="%(__file__)) 94 | # test_create_table_with_wrong_table_definition(walletPrivateKey, appKey) 95 | test_table_create_insert_nonencrypted(walletPrivateKey, appKey, GATEWAY) 96 | test_table_create_insert_encrypted(walletPrivateKey, appKey, GATEWAY) 97 | test_cocoon_create_tablelink(walletPrivateKey, appKey, GATEWAY) 98 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 99 | return False 100 | 101 | if __name__ == "__main__": 102 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) -------------------------------------------------------------------------------- /mindlakesdk/__init__.py: -------------------------------------------------------------------------------- 1 | name = "mindlakesdk" 2 | 3 | from eth_account.messages import encode_defunct 4 | from web3 import Web3 5 | import requests 6 | 7 | import mindlakesdk.settings as settings 8 | import mindlakesdk.utils 9 | from mindlakesdk.utils import ResultType, Session, DataType, BlockChain 10 | import mindlakesdk.keyhelper 11 | from mindlakesdk.datalake import DataLake 12 | from mindlakesdk.cryptor import Cryptor 13 | from mindlakesdk.permission import Permission 14 | import mindlakesdk.message 15 | 16 | import logging 17 | 18 | class MindLake(ResultType): 19 | 20 | DataType = DataType 21 | 22 | def __init__(self, walletPrivateKey: str, appKey: str, chainID: str = settings.DEFAULT_CHAINID, gateway: str = None): 23 | logging.debug(__name__) 24 | self.__session = mindlakesdk.utils.Session() 25 | session = self.__session 26 | session.requstSession = requests.Session() 27 | self.datalake = DataLake(session) 28 | self.cryptor = Cryptor(session) 29 | self.permission = Permission(session) 30 | 31 | session.chainID = chainID 32 | session.appKey = appKey 33 | if gateway: 34 | session.gateway = gateway 35 | else: 36 | session.gateway = settings.GATEWAY 37 | logging.debug('gateway: %s'%session.gateway) 38 | 39 | result = mindlakesdk.message.getChainInfo(session) 40 | if not result: 41 | self.__setResult(result) 42 | return 43 | chainInfo = result.data 44 | logging.debug('getChainInfo: %s'%result.data) 45 | chain = None 46 | for chainObj in chainInfo: 47 | if chainObj["chainId"] == chainID: 48 | chain = BlockChain(chainObj) 49 | break 50 | if not chain: 51 | self.__setResult(ResultType(60016, "Specified chainID not supported", None)) 52 | return 53 | 54 | web3 = Web3(Web3.HTTPProvider(chain.rpcNode)) 55 | walletAccount = web3.eth.account.from_key(walletPrivateKey) 56 | 57 | session.walletAddress = walletAccount.address 58 | logging.debug('walletAddress: %s'%session.walletAddress) 59 | 60 | result = mindlakesdk.message.getNounce(session) 61 | if not result: 62 | self.__setResult(result) 63 | return 64 | nounce = result.data 65 | logging.debug('getNounce: %s'%nounce) 66 | 67 | result = MindLake.__login(session, walletAccount, nounce) 68 | if not result: 69 | self.__setResult(result) 70 | return 71 | logging.debug('__login: %s'%result.data) 72 | 73 | session.mk, session.sk = mindlakesdk.keyhelper.prepareKeys(web3, walletAccount, chain) 74 | logging.debug('getNounce: %s'%nounce) 75 | 76 | result = mindlakesdk.message.getAccountInfo(session) 77 | if not result: 78 | self.__setResult(result) 79 | return 80 | 81 | result = mindlakesdk.keyhelper.registerMK(session) 82 | if not result: 83 | self.__setResult(result) 84 | return 85 | 86 | if not session.isRegistered: 87 | result = MindLake.__registerAccount(session, self.permission) 88 | if not result: 89 | self.__setResult(result) 90 | return 91 | else: 92 | result = mindlakesdk.message.getPKid(session) 93 | if not result: 94 | self.__setResult(result) 95 | return 96 | self.code = 0 97 | self.message = "Success" 98 | self.data = None 99 | 100 | @staticmethod 101 | def __login(session: Session, walletAccount, nounce): 102 | msgToSign = encode_defunct(text=nounce) 103 | signature = walletAccount.sign_message(msgToSign) 104 | signatureHex = signature.signature.hex() 105 | return mindlakesdk.message.sendLogin(session, signatureHex) 106 | 107 | @staticmethod 108 | def __registerAccount(session: Session, permission: Permission): 109 | result = mindlakesdk.keyhelper.registerPK(session) 110 | if not result: 111 | return result 112 | result = permission.grantToSelf() 113 | return result 114 | 115 | def __setResult(self, result): 116 | self.code = result.code 117 | self.message = result.message 118 | self.data = result.data 119 | 120 | def getNameSpace(self, walletAddress: str, chainID: str = None): 121 | if chainID is None: 122 | chainID = self.__session.chainID 123 | if chainID == settings.CLERK_CHAINID: 124 | return walletAddress.replace('@', '_').replace('.', '_') 125 | else: 126 | addressHex = walletAddress[2:].lower() 127 | if chainID == settings.DEFAULT_CHAINID: 128 | return addressHex 129 | else: 130 | return chainID + "_" + addressHex 131 | 132 | connect = mindlakesdk.MindLake 133 | -------------------------------------------------------------------------------- /tutorial/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Python SDK

5 | 6 |

7 | A step-by-step cookbook for beginner to access Mind Lake ! 8 |

9 |
10 | 11 | 12 | 13 | ## :notebook_with_decorative_cover: Table of Contents 14 | - [:notebook\_with\_decorative\_cover: Table of Contents](#notebook_with_decorative_cover-table-of-contents) 15 | - [:star2: 0. Other Programming Languages](#star2-0-other-programming-languages) 16 | - [:star2: 1. Install Or Upgrade Python If Needed](#star2-1-install-or-upgrade-python-if-needed) 17 | - [:star2: 2. Install MindLakeSDK](#star2-2-install-mindlakesdk) 18 | - [:star2: 3. Get Examples](#star2-3-get-examples) 19 | - [:star2: 4. Prepare env.py If Needed](#star2-4-prepare-envpy-if-needed) 20 | - [:star2: 5. Execute the examples](#star2-5-execute-the-examples) 21 | - [:art: 5.1 QuickStart](#art-51-quickstart) 22 | - [:art: 5.2 Use Case 1: Single User with Structured Data](#art-52-use-case-1-single-user-with-structured-data) 23 | - [:art: 5.3 Use Case 2: Single User with Unstructured Data](#art-53-use-case-2-single-user-with-unstructured-data) 24 | - [:art: 5.4 Use Case 3: Multi Users with Permission Sharing](#art-54-use-case-3-multi-users-with-permission-sharing) 25 | 26 | 27 | ## :star2: 0. Other Programming Languages 28 | - [TypeScript](https://github.com/mind-network/mind-lake-sdk-typescript/) 29 | 30 | ## :star2: 1. Install Or Upgrade Python If Needed 31 | - Click to view [step-by-step to configure Python](Configure_Python.md) if Python is not installed or upgraded 32 | 33 | ## :star2: 2. Install MindLakeSDK 34 | 1. Open Terminal and Enter the install command: 35 | ```shell 36 | pip install mindlakesdk 37 | ``` 38 | The required package will be automatically installed as a dependency, and get the following output in the end: 39 | ``` 40 | Installing collected packages: mindlakesdk 41 | Successfully installed mindlakesdk-1.0.1 42 | ``` 43 | 44 | 2. Validate the installation in Terminal 45 | ```shell 46 | $ pip show mindlakesdk 47 | Name: mindlakesdk 48 | Version: 1.0.1 49 | Summary: A Python SDK to connect to Mind Lake 50 | Home-page: 51 | Author: 52 | Author-email: Mind Labs 53 | License: 54 | Location: /Users/xxx/.pyenv/versions/3.10.11/lib/python3.10/site-packages 55 | Requires: eth-account, pynacl, web3 56 | Required-by: 57 | ``` 58 | 59 | 3. You can also validate in IDLE 60 | ```shell 61 | $ python3 62 | Python 3.10.11 (main, May 1 2023, 01:38:51) [Clang 16.0.2 ] on darwin 63 | Type "help", "copyright", "credits" or "license" for more information. 64 | >>> import mindlakesdk 65 | >>> 66 | ``` 67 | 68 | 69 | ## :star2: 3. Get Examples 70 | 1. Enter the following command in the terminal window to fetch the example code from github: 71 | ```shell 72 | git clone https://github.com/mind-network/mind-lake-sdk-python.git 73 | ``` 74 | 2. Enter the path of example code: 75 | ```shell 76 | cd mind-lake-sdk-python/examples 77 | ``` 78 | 79 | 80 | ## :star2: 4. Prepare env.py If Needed 81 | 1. `env.py` contains the settings of parameters used in examples and use cases, you can copy `env_template.py` to the name `env.py` and modify it as per your requirement. 82 | 83 | ![env_template](imgs/env_template.png) 84 | 85 | 2. `env.py` will need `walletAddress`, `walletPrivateKey` and `appKey`. 86 | 87 | - 2.1. Click [:art: 4.1. Prepare Wallet](Configure_Wallet.md#art-41-prepare-wallet) if test wallets (by MetaMask) are not created and `walletAddress` is not set in `env.py`. 88 | 89 | - 2.2. Click [:dart: 4.1.4 Export private key from MetaMask to env.py](Configure_Wallet.md#dart-414-export-private-key-from-metamask-to-envpy) if `walletPrivateKey` is not set in `env.py` 90 | 91 | - 2.3 Click [:art: 4.2. Prepare appKey](Configure_Wallet.md#art-42-prepare-appkey) if `appKey` is not set. 92 | 93 | 3. If you want to run the examples of QuickStart, Use Case 1 and Use Case 2, you only need to fill out `walletAddressAlice`, `walletPrivateKeyAlice` and `appKey`. 94 | 4. If you want to run Use Case 3, you need to fill out the walltes info for all of `Alice`, `Bob` and `Charlie`. 95 | 96 | ![env](imgs/env.png) 97 | 98 | 99 | ## :star2: 5. Execute the examples 100 | You can execute the following commands to run the quickstart and use cases. 101 | ### :art: 5.1 QuickStart 102 | ```shell 103 | python quickstart.py 104 | ``` 105 | ![QuickStart](imgs/quickstart.png) 106 | ### :art: 5.2 Use Case 1: Single User with Structured Data 107 | ```shell 108 | python use_case_1.py 109 | ``` 110 | ![Use Case 1](imgs/use_case_1.png) 111 | ### :art: 5.3 Use Case 2: Single User with Unstructured Data 112 | ```shell 113 | python use_case_2.py 114 | ``` 115 | You can also check the result by opening these two picture files. 116 | ![Use Case 2](imgs/use_case_2.png) 117 | 118 | ### :art: 5.4 Use Case 3: Multi Users with Permission Sharing 119 | ```shell 120 | python use_case_3.py 121 | ``` 122 | ![Use Case 3](imgs/use_case_3.png) 123 | -------------------------------------------------------------------------------- /tutorial/Configure_Wallet.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Configure Wallet

5 | 6 |

7 | A step-by-step cookbook for Wallet Configuration to access Mind Lake ! 8 |

9 |
10 | 11 | 12 | 13 | ## :notebook_with_decorative_cover: Table of Contents 14 | - [:notebook\_with\_decorative\_cover: Table of Contents](#notebook_with_decorative_cover-table-of-contents) 15 | - [:star2: 0. Step by step tutorial](#star2-0-step-by-step-tutorial) 16 | - [:star2: 4. Prepare env.py](#star2-4-prepare-envpy) 17 | - [:art: 4.1. Prepare Wallet](#art-41-prepare-wallet) 18 | - [:dart: 4.1.1 Install Wallet](#dart-411-install-wallet) 19 | - [:dart: 4.1.2 Wallet Sign In: https://scan.mindnetwork.xyz](#dart-412-wallet-sign-in-httpsscanmindnetworkxyz) 20 | - [:dart: 4.1.3 Copy the wallet address from MetaMask to env.py](#dart-413-copy-the-wallet-address-from-metamask-to-envpy) 21 | - [:dart: 4.1.4 Export private key from MetaMask to env.py](#dart-414-export-private-key-from-metamask-to-envpy) 22 | - [:art: 4.2. Prepare appKey](#art-42-prepare-appkey) 23 | - [:dart: 4.2.1 create Dapp](#dart-421-create-dapp) 24 | 25 | ## :star2: 0. Step by step tutorial 26 | This is part of support chapter for MindLake step-by-step tutorial for [Python](README.md) 27 | 28 | ## :star2: 4. Prepare env.py 29 | `env.py` contains the settings of parameters used in examples and use cases, you can copy `env_template.py` to the name `env.py` and modify it as per your requirement. 30 | If you want to run the examples of QuickStart, Use Case 1 and Use Case 2, you only need to fill out `walletAddressAlice`, `walletPrivateKeyAlice` and `appKey`. 31 | If you want to run Use Case 3, you need to fill out the walltes info for all of `Alice`, `Bob` and `Charlie`. 32 | ![env_template](imgs/env_template.png) 33 | ![env](imgs/env.png) 34 | 35 | ### :art: 4.1. Prepare Wallet 36 | 37 | #### :dart: 4.1.1 Install Wallet 38 | 1. Install [MetaMask](https://metamask.io/download/) plugins in Chrome Browser 39 | 2. [Sign up a MetaMask Wallet](https://myterablock.medium.com/how-to-create-or-import-a-metamask-wallet-a551fc2f5a6b) 40 | 3. Change the network to Goerli TestNet. If the TestNets aren't displayed, turn on "Show test networks" in Settings. 41 | ![MetaMask TestNet](imgs/metamask_testnet.png) 42 | 4. Goerli Faucet for later gas fee if does not have: [Alchemy Goerli Faucet](https://goerlifaucet.com/), [Quicknode Goerli Faucet](https://faucet.quicknode.com/ethereum/goerli), [Moralis Goerli Faucet](https://moralis.io/faucets/) 43 | __Note__ that you need to get test coin from faucets for ALL of the wallets specified in env.py 44 | 45 | ![image](./imgs/change_chain.png) 46 | 47 | #### :dart: 4.1.2 Wallet Sign In: https://scan.mindnetwork.xyz 48 | 1. Open a browser and visit [mind-scan](https://scan.mindnetwork.xyz/scan) 49 | 2. Click "Sign in" buttom 50 | 51 | ![image](./imgs/sign_scan.png) 52 | 53 | 2.1 During the 'Connect' procedure, the wallet will prompt the user 2-3 times as follows: 54 | Sign a nonce for login authentication. 55 | 56 | ![image](./imgs/nounce_sign.png) 57 | 58 | 2.2 If the user's account keys are already on the chain: Decrypt the user's account keys using the wallet's private key. 59 | 60 | ![image](./imgs/decrypt_request.png) 61 | 62 | 2.3 If the user's account keys do not exist yet: Obtain the public key of the wallet, which is used to encrypt the randomly generated account keys. 63 | 64 | ![image](./imgs/request_publickey.png) 65 | 66 | 2.4 Sign the transaction to upload the encrypted key ciphers to the smart contract on the chain. 67 | 68 | ![image](./imgs/upload_chain.png) 69 | 70 | #### :dart: 4.1.3 Copy the wallet address from MetaMask to env.py 71 | Click the copy icon beside the wallet address in UI of MetaMask, and paste into env.py 72 | #### :dart: 4.1.4 Export private key from MetaMask to env.py 73 | These are the steps outlined in the [MetaMask support documentation](https://support.metamask.io/hc/en-us/articles/360015289632-How-to-export-an-account-s-private-key). 74 | 1. Click on the identicon in the top right. 75 | 2. Select the account you'd like to export. 76 | 3. On the account page, click on the menu (three dots) in the upper right corner, and then on the "Account Details" button. 77 | 4. Click “Export Private Key”. 78 | 5. To access your private key, you'll now need to enter your wallet password. Once you've done so, click “Confirm” to proceed. 79 | 6. Your private key will now be revealed. Click to copy it, and paste into env.py. 80 | 7. Click “Done” to close the screen. 81 | 82 | ![Export Private Key](imgs/private_key.gif) 83 | 84 | 85 | 86 | ### :art: 4.2. Prepare appKey 87 | 88 | ##### :dart: 4.2.1 create Dapp 89 | 1. Click `myDapp` in left side manu 90 | 91 | ![image](./imgs/myDapp_menu.png) 92 | 93 | 2. Click "Create Dapp" 94 | 95 | ![image](./imgs/create_dapp.png) 96 | 97 | 3. Input your Dapp name and then click "Create" 98 | 99 | ![image](./imgs/create_dapp_confirm.png) 100 | 101 | 4. copy appKey value into env.py to update "appKey" 102 | 103 | ![image](./imgs/dapp_list.png) 104 | -------------------------------------------------------------------------------- /tests/test_insert.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | 5 | import logging 6 | setLogging(logging.INFO) 7 | #setLogging(logging.DEBUG) 8 | 9 | import env 10 | # logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 11 | 12 | import mindlakesdk 13 | print("============= start test ============") 14 | 15 | 16 | 17 | def test_insert_nonencrypted(mindlake: MindLake): 18 | print_test_functions() 19 | 20 | tableName = 'test_table_nonencrypted' 21 | test_base.drop_test_table(mindlake, tableName) 22 | test_base.create_test_table_nonencrypted(mindlake, tableName) 23 | count_select_all = test_base.insert_test_table_nonencrypted(mindlake, tableName) 24 | assert count_select_all == 2, "nonencrypted table insert wrong !" 25 | 26 | def test_insert_encrypted(mindlake: MindLake): 27 | print_test_functions() 28 | 29 | tableName = 'test_table_encrypted' 30 | test_base.drop_test_table(mindlake, tableName) 31 | test_base.create_test_table_encrypted(mindlake, tableName) 32 | data = { 33 | 'int4': 123, 34 | 'int8': 1234567890, 35 | 'float4': 1.2345678901234567890, 36 | 'float8': 1.2345678901234567890, 37 | 'decimal': 12345678901234567890, 38 | 'text': 'Hello', 39 | 'timestamp': datetime.datetime.now() 40 | } 41 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 42 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 43 | 44 | print("===== select * =====") 45 | queryResult = mindlake.datalake.query(f'SELECT * FROM "{tableName}"') 46 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 47 | logging.debug(queryResult.data) 48 | count_select_all = len(queryResult.data['data']) 49 | assert queryResult and count_select_all == 2, 'decryption test failed !' 50 | for row in queryResult.data['data']: 51 | columnList = queryResult.data['columnList'] 52 | for i, cell in enumerate(row): 53 | print(f"===== decrypt column num {i} {columnList[i]}=====") 54 | q = mindlake.cryptor.decrypt(cell) 55 | print('Code: ', q.code, q.message, 'DECRYPT ', columnList[i]) 56 | print('Decrypted: Column', i, columnList[i], q.data) 57 | print('Origin:', data[columnList[i][4:]]) 58 | if columnList[i] == 'datafloat4' or columnList[i] == 'datafloat8': 59 | assert q and abs(q.data - data[columnList[i][4:]]) < 0.01, 'decryption test failed !' 60 | else: 61 | assert q and q.data == data[columnList[i][4:]], 'decryption test failed !' 62 | 63 | 64 | print("============= unique with primary key ============") 65 | def test_insert_nonencrypted_unique(mindlake: MindLake): 66 | print_test_functions() 67 | 68 | tableName = 'test_table_nonencrypted_primarykey' 69 | test_base.drop_test_table(mindlake, tableName) 70 | test_base.create_test_table_nonencrypted_unique(mindlake, tableName) 71 | count_select_all = test_base.insert_test_table_nonencrypted(mindlake, tableName) 72 | assert count_select_all == 1, "nonencrypted table with primary key insert wrong !" 73 | 74 | def test_insert_encrypted_unique(mindlake: MindLake): 75 | print_test_functions() 76 | 77 | tableName = 'test_table_encrypted_primarykey' 78 | test_base.drop_test_table(mindlake, tableName) 79 | test_base.create_test_table_encrypted_unique(mindlake, tableName) 80 | data = { 81 | 'int4': 123, 82 | 'int8': 1234567890, 83 | 'float4': 1.2345678901234567890, 84 | 'float8': 1.2345678901234567890, 85 | 'decimal': 12345678901234567890, 86 | 'text': 'Hello', 87 | 'timestamp': datetime.datetime.now() 88 | } 89 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 90 | test_base.insert_test_table_encrypted(mindlake, tableName, data) 91 | 92 | print("===== select * =====") 93 | queryResult = mindlake.datalake.query(f'SELECT * FROM "{tableName}"') 94 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 95 | logging.debug(queryResult.data) 96 | count_select_all = len(queryResult.data['data']) 97 | assert queryResult and count_select_all == 1, 'decryption test failed !' 98 | for row in queryResult.data['data']: 99 | columnList = queryResult.data['columnList'] 100 | for i, cell in enumerate(row): 101 | print(f"===== decrypt column num {i} {columnList[i]}=====") 102 | q = mindlake.cryptor.decrypt(cell) 103 | print('Code: ', q.code, q.message, 'DECRYPT ', columnList[i]) 104 | print('Decrypted: Column', i, columnList[i], q.data) 105 | print('Origin:', data[columnList[i][4:]]) 106 | if columnList[i] == 'datafloat4' or columnList[i] == 'datafloat8': 107 | assert q and abs(q.data - data[columnList[i][4:]]) < 0.01, 'decryption test failed !' 108 | else: 109 | assert q and q.data == data[columnList[i][4:]], 'decryption test failed !' 110 | 111 | print("============= complete test ============") 112 | def cases(walletPrivateKey, appKey, GATEWAY): 113 | logging.info("==== start test | %s ===="%(__file__)) 114 | mindlake = mindlakesdk.connect(walletPrivateKey, appKey, GATEWAY) 115 | assert mindlake, 'mindlakesdk.connect failed !' 116 | test_base.drop_all_cocoon_and_table(mindlake) 117 | test_insert_nonencrypted(mindlake) 118 | test_insert_encrypted(mindlake) 119 | test_insert_nonencrypted_unique(mindlake) 120 | test_insert_encrypted_unique(mindlake) 121 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 122 | 123 | if __name__ == "__main__": 124 | cases(env.walletPrivateKey, env.appKey, env.GATEWAY) 125 | 126 | -------------------------------------------------------------------------------- /mindlakesdk/keyhelper.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import logging 4 | import struct 5 | import uuid 6 | from Crypto.Random import get_random_bytes 7 | from Crypto.PublicKey import RSA 8 | from web3 import Web3 9 | from mindlakesdk import settings 10 | import mindlakesdk.utils 11 | import mindlakesdk.message 12 | from base64 import b64decode, b64encode 13 | from nacl.public import Box, PrivateKey, PublicKey 14 | 15 | def registerMK(session: mindlakesdk.utils.Session): 16 | pubKey = session.nodePK.replace("\\n", "\n") 17 | ephemeralKey = get_random_bytes(16) 18 | sealedEphemeralKey = mindlakesdk.utils.rsaEncrypt(pubKey, ephemeralKey) 19 | sealedEphemeralKeyLenBytes = len(sealedEphemeralKey).to_bytes(2, 'little') 20 | iv = get_random_bytes(16) 21 | envelope_json = __genMKEnvelope(session.mk) 22 | envelope_enc = mindlakesdk.utils.aesEncrypt(ephemeralKey, iv, envelope_json.encode()) 23 | big_envelope = sealedEphemeralKeyLenBytes + sealedEphemeralKey + iv + envelope_enc 24 | envelope = base64.b64encode(big_envelope).decode() 25 | return mindlakesdk.message.sendMKRegister(session, envelope) 26 | 27 | def __genMKEnvelope(mk, accountID = None): 28 | envelope = {} 29 | if accountID: 30 | envelope["mekid"] = accountID 31 | base64MK = base64.b64encode(mk) 32 | envelope["mek"] = base64MK.decode() 33 | envelope["expire"] = 0 34 | envelope_json = json.dumps(envelope).replace(" ", "") 35 | return envelope_json 36 | 37 | def registerPK(session: mindlakesdk.utils.Session): 38 | privateKey = session.sk 39 | publicKey = privateKey.publickey().export_key("PEM").decode() 40 | pkIDBytes = __genPKid(publicKey) 41 | pkIDStr = pkIDBytes.decode() 42 | session.pkID = pkIDStr 43 | toBeSignedBytes = bytearray(struct.pack(" 2 | 3 | logo 4 |

MindLake Tutorial: Configure Python

5 | 6 |

7 | A step-by-step cookbook for Python Configuration ! 8 |

9 | 10 | 11 | ## :star2: 0. Step by step tutorial 12 | This is part of support chapter for MindLake step-by-step tutorial for [Python](README.md) 13 | 14 | ## :star2: 1. Configure Python In Your Local Environment 15 | 16 | ### :notebook_with_decorative_cover: Table of Contents 17 | - [:star2: 0. Step by step tutorial](#star2-0-step-by-step-tutorial) 18 | - [:star2: 1. Configure Python In Your Local Environment](#star2-1-configure-python-in-your-local-environment) 19 | - [:notebook\_with\_decorative\_cover: Table of Contents](#notebook_with_decorative_cover-table-of-contents) 20 | - [:art: 1.1 For Mac OS](#art-11-for-mac-os) 21 | - [:dart: 1.1.1 Choice 1: Install Python with Official Installer](#dart-111-choice-1-install-python-with-official-installer) 22 | - [:gear: 1.1.1.1 Step1: Download the Official Installer](#gear-1111-step1-download-the-official-installer) 23 | - [:gear: 1.1.1.2 Step2: Run the Installer](#gear-1112-step2-run-the-installer) 24 | - [:gear: 1.1.1.3 Step3: Verify the Python Installation](#gear-1113-step3-verify-the-python-installation) 25 | - [:dart: 1.1.2 Choice 2: Install Python with HomeBrew](#dart-112-choice-2-install-python-with-homebrew) 26 | - [:gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed)](#gear-1121-step1-install-homebrew-if-you-dont-have-homebrew-installed) 27 | - [:gear: 1.1.2.2 Step2: Install Python](#gear-1122-step2-install-python) 28 | - [:gear: 1.1.2.3 Step3: Verify the Python Installation](#gear-1123-step3-verify-the-python-installation) 29 | - [:art: 1.2 For Windows](#art-12-for-windows) 30 | - [:dart: 1.2.1 Step1: Download the Official Installer](#dart-121-step1-download-the-official-installer) 31 | - [:dart: 1.2.2 Step2: Running the Executable Installer](#dart-122-step2-running-the-executable-installer) 32 | - [:dart: 1.2.3 Step3: Verify the Python Installation](#dart-123-step3-verify-the-python-installation) 33 | 34 | ### :art: 1.1 For Mac OS 35 | 36 | #### :dart: 1.1.1 Choice 1: Install Python with Official Installer 37 | ##### :gear: 1.1.1.1 Step1: Download the Official Installer 38 | Here is an example of Python installation with official installer, you can also choose other package management tools like HomeBrew or Conda. 39 | 1. Navigate to python.org, specifically to [Downloads > MacOS](https://www.python.org/downloads/macos/) and download the latest installation file. 40 | 2. Click on the latest stable release of Python link under the latest Python releases for macOS. The latest available version of Python as of this writing is 3.11.3 41 | ![Python Releases](imgs/releases.png) 42 | 43 | 3. Now you'll be able to see the version-specific information. Scroll down on this page until you see a table with all available installation files. 44 | 4. Select and download the file that in the description says macOS under the operating system. 45 | 46 | ##### :gear: 1.1.1.2 Step2: Run the Installer 47 | ![Python installer for Mac OS](imgs/installer_macos.png) 48 | To proceed with a default installation of Python, click the "Continue" button. Default option is recommended if you're new to Python and want to install it with standard features. Simply wait for the installation to complete and then click the "Close" button. 49 | 50 | ##### :gear: 1.1.1.3 Step3: Verify the Python Installation 51 | * Find and open "Terminal" or other shell terminal in LauchPad. 52 | ![Terminal APP](imgs/terminal.jpg) 53 | * Enter the following command in terminal: 54 | ```shell 55 | python --version 56 | ``` 57 | An example of the output is: 58 | ``` 59 | Python 3.11.3 60 | ``` 61 | 62 | #### :dart: 1.1.2 Choice 2: Install Python with HomeBrew 63 | If you need to install Python from the command line on macOS, the Homebrew package manager is a reliable option. Follow the steps below to install Python via Homebrew: 64 | ##### :gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed) 65 | 1. Open a browser and go to https://brew.sh. 66 | ![Homebrew](imgs/homebrew.png) 67 | 2. Under the "Install Homebrew" title, copy the command 68 | ```shell 69 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 70 | ``` 71 | ![Homebrew Install](imgs/install_brew.png) 72 | 3. Then open a terminal window, paste the copied command, and press the 'Enter' or 'Return' button. 73 | ![Terminal APP](imgs/terminal.jpg) 74 | 4. Enter your macOS credentials if and when asked. 75 | 5. If prompted, install Apple's command line developer tools. 76 | 6. If prompted, copy, paste and execute the command to add Homebrew to your shell profile file. 77 | ![Homebrew Post Install](imgs/brew_post_install.png) 78 | 79 | ##### :gear: 1.1.2.2 Step2: Install Python 80 | 1. Enter the following command in terminal to upgrade Homebrew: 81 | ```shell 82 | brew update && brew upgrade 83 | ``` 84 | 2. Install Python using this command: 85 | ```shell 86 | brew install python3 87 | ``` 88 | 89 | ![Install Python](imgs/install_complete.png) 90 | 91 | 92 | ##### :gear: 1.1.2.3 Step3: Verify the Python Installation 93 | Enter the following command in terminal: 94 | ```shell 95 | python --version 96 | ``` 97 | An example of the output is: 98 | ``` 99 | Python 3.11.3 100 | ``` 101 | 102 | 103 | ### :art: 1.2 For Windows 104 | #### :dart: 1.2.1 Step1: Download the Official Installer 105 | Here is an example of Python installation with official installer, you can also choose other package management tools like Anaconda. 106 | 1. Go to the official Python download page for Windows. 107 | 2. Find a stable Python 3 release. This tutorial was tested with Python version 3.11.3. 108 | 3. Click the appropriate link for your system to download the executable file: Windows installer (64-bit) or Windows installer (32-bit). 109 | 110 | #### :dart: 1.2.2 Step2: Running the Executable Installer 111 | 1. After the installer is downloaded, double-click the .exe file, for example python-3.11.3-amd64.exe, to run the Python installer. 112 | 2. Select the __Add python.exe to PATH__ checkbox, which enables users to launch Python from the command line. 113 | ![Python installer add path](imgs/python_install_path.png) 114 | 3. If you’re just getting started with Python and you want to install it with default features as described in the dialog, then click __Install Now__. 115 | 4. Simply wait for the installation to complete and then click the __Close__ button. 116 | ![Python installer complete](imgs/python_install_complete.png) 117 | 118 | #### :dart: 1.2.3 Step3: Verify the Python Installation 119 | Press __Win(⊞) + r__ in keyboard and enter `cmd` to open the command prompt. 120 | ![Windows Command Prompt](imgs/windows_cmd.png) 121 | Enter the following command in the command prompt: 122 | ```cmd 123 | python --version 124 | ``` 125 | An example of the output is: 126 | ``` 127 | Python 3.11.3 128 | ``` 129 | -------------------------------------------------------------------------------- /tests/test_link.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import test_base 4 | import mindlakesdk 5 | 6 | 7 | import logging 8 | setLogging(logging.INFO) 9 | # setLogging(logging.DEBUG) 10 | 11 | import env 12 | #logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 13 | 14 | print("============= start test ============") 15 | 16 | 17 | print("============= link encrypted table ============") 18 | def test_link_table_to_mywalletcocoon_alice_encrypted(mindlake: MindLake): 19 | print_test_functions() 20 | 21 | tableNameAlice = 'test_table_encrypted_Alice' 22 | test_base.drop_test_table(mindlake, tableNameAlice) 23 | test_base.create_test_table_encrypted(mindlake, tableNameAlice) 24 | data = { 25 | 'int4': 123, 26 | 'int8': 1234567890, 27 | 'float4': 1.2345678901234567890, 28 | 'float8': 1.2345678901234567890, 29 | 'decimal': 12345678901234567890, 30 | 'text': 'Hello', 31 | 'timestamp': datetime.datetime.now() 32 | } 33 | q = test_base.insert_test_table_encrypted(mindlake, tableNameAlice, data) 34 | 35 | cocoonName = "test_cocoon_alice_encrypted" 36 | test_base.create_test_cocoon(mindlake, cocoonName) 37 | code = test_base.link_table_to_cocoon_test(mindlake, tableNameAlice, cocoonName) 38 | assert code == 0, "alice can not link her table to her cocoon !" 39 | 40 | def test_link_table_to_otherwalletcocoon_bob_encrypted(mindlake: MindLake): 41 | print_test_functions() 42 | 43 | tableNameAlice = 'test_table_encrypted_Alice' 44 | cocoonName = "test_cocoon_bob_encrypted" 45 | test_base.create_test_cocoon(mindlake, cocoonName) 46 | code = test_base.link_table_to_cocoon_test(mindlake, tableNameAlice, cocoonName) 47 | assert code != 0, "bob can link alice's table to his cocoon !" 48 | # also need to check error code 49 | 50 | 51 | 52 | 53 | print("============= link nonencrypted table ============") 54 | def test_link_table_to_mywalletcocoon_alice_nonencrypted(mindlake: MindLake): 55 | print_test_functions() 56 | 57 | tableNameAlice = 'test_table_nonencrypted_Alice' 58 | test_base.drop_test_table(mindlake, tableNameAlice) 59 | test_base.create_test_table_nonencrypted(mindlake, tableNameAlice) 60 | test_base.insert_test_table_nonencrypted(mindlake, tableNameAlice) 61 | 62 | cocoonName = "test_cocoon_alice_nonencrypted" 63 | test_base.create_test_cocoon(mindlake, cocoonName) 64 | code = test_base.link_table_to_cocoon_test(mindlake, tableNameAlice, cocoonName) 65 | assert code == 0, "alice can not link her table to her cocoon !" 66 | 67 | def test_link_table_to_otherwalletcocoon_bob_nonencrypted(mindlake: MindLake): 68 | print_test_functions() 69 | 70 | tableNameAlice = 'test_table_nonencrypted_Alice' 71 | cocoonName = "test_cocoon_bob_nonencrypted" 72 | test_base.create_test_cocoon(mindlake, cocoonName) 73 | code = test_base.link_table_to_cocoon_test(mindlake, tableNameAlice, cocoonName) 74 | assert code != 0, "bob can link alice's table to his cocoon !" 75 | # also need to check error code 76 | 77 | 78 | 79 | print("============= link nonencrypted table ============") 80 | def test_unique_link(mindlake: MindLake): 81 | print_test_functions() 82 | 83 | cocoonName1 = "test_cocoon_alice_nonencrypted" 84 | result = mindlake.datalake.listTablesByCocoon(cocoonName1) 85 | logging.warning('listTablesByCocoon: %s %s %s'%(cocoonName1, result.code, result.message)) 86 | tables = result.data 87 | logging.debug(tables) 88 | tableNameAlice1 = 'test_table_nonencrypted_Alice' 89 | assert tableNameAlice1 in tables, 'table should be linked to cocoon !' 90 | 91 | cocoonName2 = "test_cocoon_alice_encrypted" 92 | result = mindlake.datalake.listTablesByCocoon(cocoonName2) 93 | logging.warning('listTablesByCocoon: %s %s %s'%(cocoonName2, result.code, result.message)) 94 | tables = result.data 95 | logging.debug(tables) 96 | tableNameAlice2 = 'test_table_encrypted_Alice' 97 | assert tableNameAlice2 in tables, 'table should be linked to cocoon !' 98 | 99 | code = test_base.link_table_to_cocoon_test(mindlake, tableNameAlice1, cocoonName2) 100 | cocoonName2 = "test_cocoon_alice_encrypted" 101 | result = mindlake.datalake.listTablesByCocoon(cocoonName2) 102 | logging.warning('listTablesByCocoon: %s %s %s'%(cocoonName2, result.code, result.message)) 103 | tables = result.data 104 | tableNameAlice1 = 'test_table_encrypted_Alice' 105 | assert tableNameAlice1 in tables, 'table should be linked to other cocoon !' 106 | 107 | cocoonName1 = "test_cocoon_alice_nonencrypted" 108 | result = mindlake.datalake.listTablesByCocoon(cocoonName1) 109 | logging.warning('listTablesByCocoon: %s %s %s'%(cocoonName1, result.code, result.message)) 110 | tables = result.data 111 | tableNameAlice1 = 'test_table_nonencrypted_Alice' 112 | assert tableNameAlice1 not in tables, 'table should be linked to other cocoon and not exist in previous cocoon!' 113 | 114 | 115 | print("============= link non-existing table ============") 116 | def test_link_nonexist_table(mindlake: MindLake): 117 | print_test_functions() 118 | 119 | tableName = 'test_table_nonencrypted_nobody' 120 | cocoonName = "test_cocoon_alice_encrypted" 121 | 122 | code = test_base.link_table_to_cocoon_test(mindlake, tableName, cocoonName) 123 | 124 | assert code != 0, "non exist table can not be linked !" 125 | 126 | 127 | print("============= link non-existing cocoon ============") 128 | def test_link_nonexist_cocoon(mindlake: MindLake): 129 | print_test_functions() 130 | 131 | tableName = 'test_cocoon_alice_encrypted' 132 | cocoonName = "test_cocoon_nobody_encrypted" 133 | 134 | code = test_base.link_table_to_cocoon_test(mindlake, tableName, cocoonName) 135 | 136 | assert code != 0, "non exist cocoon can not be linked !" 137 | 138 | 139 | 140 | print("============= complete 4 ============") 141 | def cases(walletPrivateKeyAlice, walletPrivateKeyBob, walletAddressAlice, walletAddressBob, appKey, GATEWAY): 142 | logging.info("==== start test | %s ===="%(__file__)) 143 | mindlakeAlice = mindlakesdk.connect(walletPrivateKeyAlice, appKey, GATEWAY) 144 | mindlakeBob = mindlakesdk.connect(walletPrivateKeyBob, appKey, GATEWAY) 145 | test_base.drop_all_cocoon_and_table(mindlakeAlice) 146 | test_base.drop_all_cocoon_and_table(mindlakeBob) 147 | 148 | test_link_table_to_mywalletcocoon_alice_encrypted(mindlakeAlice) 149 | test_link_table_to_mywalletcocoon_alice_nonencrypted(mindlakeAlice) 150 | 151 | test_link_table_to_otherwalletcocoon_bob_nonencrypted(mindlakeBob) 152 | test_link_table_to_otherwalletcocoon_bob_encrypted(mindlakeBob) 153 | 154 | test_unique_link(mindlakeAlice) 155 | 156 | test_link_nonexist_table(mindlakeAlice) 157 | test_link_nonexist_cocoon(mindlakeAlice) 158 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 159 | 160 | if __name__ == "__main__": 161 | cases(env.walletPrivateKeyAlice, env.walletPrivateKeyBob, env.walletAddressAlice, env.walletAddressBob, env.appKey, env.GATEWAY) 162 | 163 | -------------------------------------------------------------------------------- /mindlakesdk/permission.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import datetime 3 | import json 4 | import logging 5 | import uuid 6 | 7 | from mindlakesdk.utils import ResultType, Session 8 | import mindlakesdk.utils 9 | import mindlakesdk.message 10 | 11 | class Permission: 12 | def __init__(self, session: Session): 13 | self.__session = session 14 | 15 | def grant(self, targetWalletAddress: str, columns: list, targetChainID: str = None) -> ResultType: 16 | session = self.__session 17 | if not targetChainID: 18 | targetChainID = session.chainID 19 | result = mindlakesdk.message.getPKidByWalletAddress(session, targetWalletAddress, targetChainID) 20 | if not result: 21 | return result 22 | targetPKID = result.data["publicKeyId"] 23 | logging.debug("Get PK ID from wallet address: " + targetWalletAddress + ' - ' + targetPKID) 24 | policy = { 25 | 'issuer_dek_group': [], 26 | 'subject_dek_group': [] 27 | } 28 | for column in columns: 29 | tableName, columnName = column.split('.') 30 | result = mindlakesdk.message.getDKbyName(session, session.walletAddress, tableName, columnName) 31 | if not result: 32 | return result 33 | groupID = result.data['groupId'] 34 | policy['issuer_dek_group'].append({ 35 | 'groupid': groupID, 36 | 'min': 1, 37 | 'max': 1000 38 | }) 39 | result = self.__createPolicyBody(self.__session.pkID, targetPKID, policy) 40 | if not result: 41 | return result 42 | policyBodyJson = result.data 43 | signature = self.__signPolicy(policyBodyJson) 44 | signatureB64 = base64.b64encode(signature).decode('utf-8') 45 | return mindlakesdk.message.sendGrant(self.__session, policyBodyJson, signatureB64) 46 | 47 | def confirm(self, policyID: str) -> ResultType: 48 | result = mindlakesdk.message.getPolicyBySerialNumber(self.__session, policyID) 49 | if not result: 50 | return result 51 | policyBodyJson = result.data 52 | policyBody = json.loads(policyBodyJson) 53 | if policyBody['subject_pukid'] != self.__session.pkID: 54 | logging.debug("Self pukid: " + self.__session.pkID) 55 | logging.debug("Policy pukid: " + policyBody['subject_pukid']) 56 | return mindlakesdk.utils.ResultType(60002, "The policy is not for you") 57 | signature = self.__signPolicy(policyBodyJson) 58 | signatureB64 = base64.b64encode(signature).decode('utf-8') 59 | return mindlakesdk.message.sendConfirm(self.__session, policyBodyJson, signatureB64) 60 | 61 | def revoke(self, targetWalletAddress: str, targetChainID: str = None) -> ResultType: 62 | return self.grant(targetWalletAddress, [], targetChainID) 63 | 64 | def grantToSelf(self): 65 | result = mindlakesdk.message.getDKbyName(self.__session) 66 | if not result: 67 | return result 68 | groupID = result.data['groupId'] 69 | policy = { 70 | 'issuer_dek_group': [{ 71 | 'groupid': groupID, 72 | 'min': 1, 73 | 'max': 1000 74 | }], 75 | 'subject_dek_group': [{ 76 | 'groupid': groupID, 77 | 'min': 1, 78 | 'max': 1000 79 | }] 80 | } 81 | result = self.__createPolicyBody(self.__session.pkID, 82 | self.__session.pkID, 83 | policy) 84 | if not result: 85 | return result 86 | policyBodyJson = result.data 87 | signature = self.__signPolicy(policyBodyJson) 88 | signatureB64 = base64.b64encode(signature).decode('utf-8') 89 | return mindlakesdk.message.sendSelfGrant(self.__session, policyBodyJson, signatureB64) 90 | 91 | def __createPolicyBody(self, 92 | issuerPukid, 93 | subjectPukid, 94 | policy, 95 | version = 1, 96 | serialNum = None, 97 | notBefore = None, 98 | notAfter = None, 99 | resultDK = "SUBJECT", 100 | operation = ['*'], 101 | postProc = "NULL", 102 | preProc = "NULL") -> ResultType: 103 | result = mindlakesdk.message.getPolicyByPKid(self.__session, issuerPukid, subjectPukid) 104 | if not result: 105 | return result 106 | policyBodyJson = result.data 107 | if policyBodyJson: 108 | logging.debug("Policy already exists, loading existing Policy") 109 | policyBody = json.loads(policyBodyJson) 110 | else: 111 | if not serialNum: 112 | serialNum = str(uuid.uuid4()) 113 | if not notBefore: 114 | notBefore = datetime.datetime.now() 115 | if not notAfter: 116 | notAfter = datetime.datetime.now() + datetime.timedelta(days=365) 117 | policyBody = {} 118 | policyBody['version'] = version 119 | policyBody['serial_num'] = serialNum 120 | policyBody['issuer_pukid'] = issuerPukid 121 | policyBody['subject_pukid'] = subjectPukid 122 | policyBody['validity'] = {} 123 | policyBody['validity']['not_after'] = notAfter.astimezone().strftime('%Y%m%d%H%M%S%z') 124 | policyBody['validity']['not_before'] = notBefore.astimezone().strftime('%Y%m%d%H%M%S%z') 125 | policyBody['policies'] = {} 126 | policyBody['policies']['operation'] = operation 127 | policyBody['policies']['post_proc'] = postProc 128 | policyBody['policies']['pre_proc'] = preProc 129 | policyBody['policies']['result_dek'] = resultDK 130 | policyBody['policies'].update(policy) 131 | policyBodyJson = json.dumps(policyBody) 132 | return ResultType(0, "Success", policyBodyJson) 133 | 134 | def __signPolicy(self, policyBodyJson) -> bytes: 135 | toBeSignedBytes = policyBodyJson.encode('utf-8') 136 | signature = mindlakesdk.utils.rsaSign(self.__session.sk, toBeSignedBytes) 137 | return b'\x01' + signature 138 | 139 | def listGrantee(self) -> ResultType: 140 | return mindlakesdk.message.sendListGrantee(self.__session) 141 | 142 | def listGrantedColumn(self, walletAddress: str, targetChainID: str = None) -> ResultType: 143 | if not targetChainID: 144 | targetChainID = self.__session.chainID 145 | return mindlakesdk.message.sendListGrantedColumn(self.__session, walletAddress, targetChainID) 146 | 147 | def listOwner(self) -> ResultType: 148 | return mindlakesdk.message.sendListOwner(self.__session) 149 | 150 | def listOwnerColumn(self, walletAddress: str, targetChainID: str = None) -> ResultType: 151 | if not targetChainID: 152 | targetChainID = self.__session.chainID 153 | return mindlakesdk.message.sendListOwnerColumn(self.__session, walletAddress, targetChainID) 154 | -------------------------------------------------------------------------------- /tests/test_sharing.py: -------------------------------------------------------------------------------- 1 | from test_base import * 2 | 3 | import datetime 4 | import pprint 5 | import test_base 6 | # test_base.drop_all_cocoon_and_table(env.walletPrivateKey) 7 | import mindlakesdk 8 | 9 | import logging 10 | setLogging(logging.INFO) 11 | # setLogging(logging.DEBUG) 12 | 13 | import env 14 | #logging.warning('walletPrivateKey = %s'%env.walletPrivateKey) 15 | 16 | print("============= start test ============") 17 | 18 | def prepare_test(mindlake: MindLake): 19 | tableName = 'test_table_encrypted_1' 20 | test_base.drop_test_table(mindlake, tableName) 21 | test_base.create_test_table_encrypted(mindlake, tableName) 22 | 23 | # by now, I should have at least one table and one cocoon, and table link to cocoon 24 | print("============= test preparation completed ============") 25 | 26 | def test_insert_encrypted_data(mindlake: MindLake, data): 27 | tableName = 'test_table_encrypted_1' 28 | encryptedData = {} 29 | for k, v in data.items(): 30 | print(f"===== encrypt {k} in column =====") 31 | currentColumn = 'data' + k 32 | q = mindlake.cryptor.encrypt(v, f'{tableName}.{currentColumn}') 33 | print('Code: ', q.code, q.message, f'encrypt {k}') 34 | assert q and isinstance(q.data, str) and q.data[:2] == '\\x', 'encryption test failed !' 35 | encryptedData[currentColumn] = q.data 36 | 37 | sql = f"""INSERT INTO "{tableName}" 38 | VALUES ( 39 | '{encryptedData['dataint4']}', 40 | '{encryptedData['dataint8']}', 41 | '{encryptedData['datafloat4']}', 42 | '{encryptedData['datafloat8']}', 43 | '{encryptedData['datadecimal']}', 44 | '{encryptedData['datatext']}', 45 | '{encryptedData['datatimestamp']}') RETURNING *""" 46 | print(sql) 47 | q = mindlake.datalake.query(sql) 48 | print('Code: ', q.code, q.message, 'INSERT') 49 | assert q, 'encryption test failed !' 50 | # logging.debug(q.data) 51 | 52 | def test_query_decrypt_shared(mindlake: MindLake, ownerWalletAddress, data, grantColumn): 53 | tableName = 'test_table_encrypted_1' 54 | 55 | print("===== select * =====") 56 | queryResult = mindlake.datalake.query(f'SELECT * FROM "{ownerWalletAddress[2:].lower()}"."{tableName}"') 57 | print('Code: ', queryResult.code, queryResult.message, 'SELECT') 58 | # logging.debug(queryResult.data) 59 | count_select_all = len(queryResult.data['data']) 60 | assert queryResult and count_select_all == 2, 'decryption test failed !' 61 | row = queryResult.data['data'][1] 62 | columnList = queryResult.data['columnList'] 63 | for i, cell in enumerate(row): 64 | print(f"===== decrypt column num {i} {columnList[i]} ===== {cell}") 65 | q = mindlake.cryptor.decrypt(cell) 66 | print('Code: ', q.code, q.message, 'DECRYPT ', columnList[i]) 67 | print('Decrypted: Column', i, columnList[i], q.data) 68 | print('Origin:', data[columnList[i][4:]]) 69 | if columnList[i] == 'datafloat4' or columnList[i] == 'datafloat8': 70 | if columnList[i][4:] in grantColumn: 71 | assert q and abs(q.data - data[columnList[i][4:]]) < 0.01, 'decryption test failed !' 72 | else: 73 | assert q.code == 60003, 'decryption test failed !' 74 | else: 75 | if columnList[i][4:] in grantColumn: 76 | assert q and q.data == data[columnList[i][4:]], 'decryption test failed !' 77 | else: 78 | assert q.code == 60003, 'decryption test failed !' 79 | 80 | def test_share_column(mindlake: MindLake, granteeWalletAddress, grantColumn): 81 | tableName = 'test_table_encrypted_1' 82 | print(f"===== share column {' '.join(grantColumn)} =====") 83 | q = mindlake.permission.grant(granteeWalletAddress, [tableName + '.data' + column for column in grantColumn]) 84 | print('Code: ', q.code, q.message, 'SHARE COLUMN') 85 | print('serialNum: ', q.data) 86 | assert q, 'share column failed !' 87 | return q.data 88 | 89 | def test_confirm_share(mindlake: MindLake, serialNum): 90 | print(f"===== confirm share {serialNum} =====") 91 | q = mindlake.permission.confirm(serialNum) 92 | print('Code: ', q.code, q.message, 'CONFIRM SHARE') 93 | assert q, 'confirm share failed !' 94 | 95 | def test_revoke_share(mindlake: MindLake, granteeWalletAddress): 96 | print(f"===== revoke share =====") 97 | q = mindlake.permission.revoke(granteeWalletAddress) 98 | print('Code: ', q.code, q.message, 'REVOKE SHARE') 99 | assert q.code == 0, 'revoke share failed !' 100 | 101 | 102 | def test_share_to_nobody(mindlake:MindLake, grantColumn, walletAddressNobody): 103 | tableName = 'test_table_encrypted_1' 104 | print(f"===== share column {' '.join(grantColumn)} =====") 105 | q = mindlake.permission.grant(walletAddressNobody, [tableName + '.data' + column for column in grantColumn]) 106 | print('Code: ', q.code, q.message, 'SHARE COLUMN') 107 | print('serialNum: ', q.data) 108 | assert q.code == 0, 'can not share to nobody !' 109 | return q.data 110 | 111 | 112 | def test_list_guarantee(): 113 | pass 114 | 115 | def cases(walletPrivateKeyAlice, walletPrivateKeyBob, walletAddressAlice, walletAddressBob, appKey, GATEWAY): 116 | logging.info("==== start test | %s ===="%(__file__)) 117 | 118 | mindlakeAlice = mindlakesdk.connect(walletPrivateKeyAlice, appKey, GATEWAY) 119 | assert mindlakeAlice, 'connect failed !' 120 | mindlakeBob = mindlakesdk.connect(walletPrivateKeyBob, appKey, GATEWAY) 121 | assert mindlakeBob, mindlakeBob.message 122 | test_base.drop_all_cocoon_and_table(mindlakeAlice) 123 | prepare_test(mindlakeAlice) 124 | data = { 125 | 'int4': 123, 126 | 'int8': 1234567890, 127 | 'float4': 1.2345678901234567890, 128 | 'float8': 1.2345678901234567890, 129 | 'decimal': 12345678901234567890, 130 | 'text': 'Hello', 131 | 'timestamp': datetime.datetime.now() 132 | } 133 | test_insert_encrypted_data(mindlakeAlice, data) 134 | test_insert_encrypted_data(mindlakeAlice, data) 135 | r = mindlakeAlice.permission.revoke(walletAddressBob) 136 | assert r, r.message 137 | print("No permission granted. Attemp to decrypt all columns") 138 | test_query_decrypt_shared(mindlakeBob, walletAddressAlice, data, []) 139 | print("============= complete 1 ============") 140 | 141 | grantColumn = ['int4'] 142 | serialNum = test_share_column(mindlakeAlice, walletAddressBob, grantColumn) 143 | test_confirm_share(mindlakeBob, serialNum) 144 | print(f"Permission of {' '.join(grantColumn)} granted. Attemp to decrypt all columns") 145 | test_query_decrypt_shared(mindlakeBob, walletAddressAlice, data, grantColumn) 146 | print("============= complete 2 ============") 147 | 148 | grantColumn = ['int8', 'float4', 'float8'] 149 | serialNum = test_share_column(mindlakeAlice, walletAddressBob, grantColumn) 150 | print(f"Permission of {' '.join(grantColumn)} granted. Attemp to decrypt all columns") 151 | test_confirm_share(mindlakeBob, serialNum) 152 | test_query_decrypt_shared(mindlakeBob, walletAddressAlice, data, grantColumn) 153 | print("============= complete 3 ============") 154 | 155 | test_revoke_share(mindlakeAlice, walletAddressBob) 156 | print(f"Revoke all permission. Attemp to decrypt all columns") 157 | test_query_decrypt_shared(mindlakeBob, walletAddressAlice, data, []) 158 | print("============= complete 4 ============") 159 | 160 | logging.info("==== complete test | %s ====\n\n"%(__file__)) 161 | 162 | if __name__ == "__main__": 163 | cases(env.walletPrivateKeyAlice, env.walletPrivateKeyBob, env.walletAddressAlice, env.walletAddressBob, env.appKey, env.GATEWAY) -------------------------------------------------------------------------------- /mindlakesdk/message.py: -------------------------------------------------------------------------------- 1 | from mindlakesdk.utils import ResultType, request, Session 2 | import logging 3 | 4 | def getNounce(session: Session): 5 | data = {"bizType":203} 6 | result = request(data, session) 7 | if result: 8 | return ResultType(result["code"], result["message"], result["data"]) 9 | else: 10 | return ResultType(60001, "Network error", None) 11 | 12 | 13 | def sendLogin(session: Session, signatureHex): 14 | data = {"bizType":201,"walletAddress":session.walletAddress,"signature":signatureHex} 15 | result = request(data, session) 16 | if result: 17 | if result["code"] == 0: 18 | # TODO: add exception handling 19 | session.isLogin = True 20 | session.token = result["data"]["token"] 21 | return ResultType(result["code"], result["message"], result["data"]) 22 | else: 23 | return ResultType(60001, "Network error", None) 24 | 25 | def getChainInfo(session: Session): 26 | data = {"bizType":205} 27 | result = request(data, session) 28 | if result: 29 | return ResultType(result["code"], result["message"], result["data"]) 30 | else: 31 | return ResultType(60001, "Network error", None) 32 | 33 | def getAccountInfo(session: Session): 34 | data = {"bizType":120} 35 | result = __requestCommon(session, data) 36 | if result: 37 | session.isRegistered = result.data["isRegistered"] and result.data["isMekProvision"] and result.data["isSelfBcl"] 38 | session.accountID = int(result.data["mekId"]) 39 | session.nodePK = result.data["publicKey"] 40 | return result 41 | 42 | def getPKid(session: Session): 43 | data = {"bizType":103,"mekId":session.accountID} 44 | result = __requestCommon(session, data) 45 | if result: 46 | session.pkID = result.data 47 | return result 48 | 49 | def sendMKRegister(session: Session, envelope): 50 | data = {"bizType":102, "envelope":envelope, "databasePublicKey":session.nodePK} 51 | return __requestCommon(session, data) 52 | 53 | def sendPKRegister(session: Session, pkIDStr, publicKey, rsaSigStr, mkSigStr): 54 | data = { 55 | "bizType":104, 56 | "mekId":session.accountID, 57 | "pukId":pkIDStr, 58 | "publicKey":publicKey, 59 | "privateSig":rsaSigStr, 60 | "mekSig":mkSigStr 61 | } 62 | return __requestCommon(session, data) 63 | 64 | def getDKbyName(session: Session, walletAddress: str = None, 65 | table: str = None, column: str = None) -> ResultType: 66 | data = {"bizType":108, "walletAddress":walletAddress, "table":table, "column":column} 67 | return __requestCommon(session, data) 68 | 69 | def getNextDKid(session: Session) -> ResultType: 70 | data = {"bizType":109, "mekId":session.accountID} 71 | return __requestCommon(session, data) 72 | 73 | def getDKbyCid(session: Session, cid: int) -> ResultType: 74 | data = {"bizType":111, "ctxId":cid} 75 | return __requestCommon(session, data) 76 | 77 | def sendDKRegister(session: Session, table, column, dkID, dkCipherStr, grpIDStr, gAuthStr) -> ResultType: 78 | data = { 79 | "bizType":110, 80 | "mekId":session.accountID, 81 | "table":table, 82 | "column":column, 83 | "dekId":dkID, 84 | "dekCipherStr":dkCipherStr, 85 | "grpIdStr":grpIDStr, 86 | "groupAuthStr":gAuthStr 87 | } 88 | return __requestCommon(session, data) 89 | 90 | def getDataTypeByName(session: Session, tableName: str, columnName: str) -> ResultType: 91 | data = {"bizType":107, "tableName":tableName, "column":columnName} 92 | return __requestCommon(session, data) 93 | 94 | def getPolicyByPKid(session: Session, issuePKid: str, subjectPKid: str) -> ResultType: 95 | data = {"bizType":118, "issuePukId":issuePKid, "subjectPukId":subjectPKid} 96 | return __requestCommon(session, data) 97 | 98 | def getPolicyBySerialNumber(session: Session, serialNumber: str) -> ResultType: 99 | data = {"bizType":116, "serialNum":serialNumber} 100 | return __requestCommon(session, data) 101 | 102 | def getPKidByWalletAddress(session: Session, walletAddress: str, targetChainID: str) -> ResultType: 103 | data = {"bizType":119, "targetWalletAddress":walletAddress, "targetChain":targetChainID} 104 | return __requestCommon(session, data) 105 | 106 | def sendGrant(session: Session, policyBodyJson: str, signature: str) -> ResultType: 107 | data = {"bizType":115, "bclBody":policyBodyJson, "privateSig":signature} 108 | return __requestCommon(session, data) 109 | 110 | def sendConfirm(session: Session, policyBodyJson: str, signature: str) -> ResultType: 111 | data = {"bizType":117, "bclBody":policyBodyJson, "privateSig":signature} 112 | return __requestCommon(session, data) 113 | 114 | def sendSelfGrant(session: Session, policyBodyJson: str, signature: str) -> ResultType: 115 | data = {"bizType":106, "bclBody":policyBodyJson, "privateSig":signature} 116 | return __requestCommon(session, data) 117 | 118 | def sendCreateTable(session: Session, tableName: str, columns: list, primaryKey: list = None) -> ResultType: 119 | data = {"bizType":123, "tableName":tableName, "columns":columns, "pkColumns":primaryKey} 120 | return __requestCommon(session, data) 121 | 122 | def sendListCocoon(session: Session) -> ResultType: 123 | data = {"bizType":122} 124 | return __requestCommon(session, data) 125 | 126 | def sendListTablesByCocoon(session: Session, cocoonName: str) -> ResultType: 127 | data = {"bizType":125, 'cocoonName': cocoonName} 128 | return __requestCommon(session, data) 129 | 130 | def sendLinkTableToCocoon(session: Session, tableName: str, cocoonName: str) -> ResultType: 131 | data = {"bizType":124, 'tableName': tableName, 'cocoonName': cocoonName} 132 | return __requestCommon(session, data) 133 | 134 | def sendCreateCocoon(session: Session, cocoonName: str) -> ResultType: 135 | data = {"bizType":121, 'cocoonName': cocoonName} 136 | return __requestCommon(session, data) 137 | 138 | def sendQuery(session: Session, executeSql) -> ResultType: 139 | data = {"bizType":114, 'executeSql': executeSql} 140 | return __requestCommon(session, data) 141 | 142 | def sendQueryForDataAndMeta(session: Session, executeSql) -> ResultType: 143 | data = {"bizType":113, 'executeSql': executeSql} 144 | return __requestCommon(session, data) 145 | 146 | def sendDropCocoon(session: Session, cocoonName: str) -> ResultType: 147 | data = {"bizType":129, 'cocoonName': cocoonName} 148 | return __requestCommon(session, data) 149 | 150 | def sendDropTable(session: Session, tableName: str) -> ResultType: 151 | data = {"bizType":128, 'tableName': tableName} 152 | return __requestCommon(session, data) 153 | 154 | def sendListGrantee(session: Session) -> ResultType: 155 | data = {"bizType":126} 156 | return __requestCommon(session, data) 157 | 158 | def sendListGrantedColumn(session: Session, targetWalletAddress: str, targetChainID: str) -> ResultType: 159 | data = {"bizType":127, 'targetWalletAddress': targetWalletAddress, 'targetChain': targetChainID} 160 | return __requestCommon(session, data) 161 | 162 | def sendListOwner(session: Session) -> ResultType: 163 | data = {"bizType":130} 164 | return __requestCommon(session, data) 165 | 166 | def sendListOwnerColumn(session: Session, targetWalletAddress: str, targetChainID: str) -> ResultType: 167 | data = {"bizType":131, 'targetWalletAddress': targetWalletAddress, 'targetChain': targetChainID} 168 | return __requestCommon(session, data) 169 | 170 | def __requestCommon(session: Session, data: str) -> ResultType: 171 | if session.isLogin: 172 | result = request(data, session) 173 | if result: 174 | return ResultType(result["code"], result["message"], result["data"]) 175 | else: 176 | return ResultType(60001, "Network error", None) 177 | else: 178 | return ResultType(40001, "Not logged in", None) -------------------------------------------------------------------------------- /mindlakesdk/cryptor.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from decimal import Decimal 3 | import struct 4 | from enum import Enum 5 | from Crypto.Random import get_random_bytes 6 | from mindlakesdk.utils import ResultType, Session, DataType 7 | import mindlakesdk.message 8 | import mindlakesdk.keyhelper 9 | import mindlakesdk.utils 10 | 11 | class Cryptor: 12 | 13 | class EncType(Enum): 14 | enc_int4 = 1 15 | enc_int8 = 2 16 | enc_float4 = 3 17 | enc_float8 = 4 18 | enc_decimal = 6 19 | enc_text = 7 20 | enc_timestamp = 8 21 | 22 | def __init__(self, session: Session): 23 | self.__session = session 24 | self.cryptParamsByColumn = {} 25 | self.cryptParamsByID = {} 26 | 27 | def encrypt(self, data, columnOrType) -> ResultType: 28 | result = self.__getEncryptParams(columnOrType) 29 | if not result: 30 | return result 31 | ctxid, encTypeNum, dk, alg, dataType = result.data 32 | data = Cryptor.__encodeByDataType(data, dataType) 33 | header = Cryptor.__genCryptoHeader(ctxid, encTypeNum) 34 | checkCode = Cryptor.__genCheckCode(data, 1) 35 | data_to_enc = data + checkCode 36 | if alg == 3: 37 | iv = get_random_bytes(16) 38 | encrypted_data = mindlakesdk.utils.aesEncrypt(dk, iv, data_to_enc) 39 | elif alg == 0: 40 | iv = get_random_bytes(12) 41 | encrypted_data = mindlakesdk.utils.aesGCMEncrypt(dk, iv, data_to_enc) 42 | buf = header + iv + encrypted_data 43 | tmp = buf[1:] 44 | checkCode2 = Cryptor.__genCheckCode(tmp, 1) 45 | result = checkCode2 + tmp 46 | resultHex = '\\x' + result.hex() 47 | return ResultType(0, None, resultHex) 48 | 49 | def __getEncryptParams(self, columnOrType): 50 | result = self.cryptParamsByColumn.get(columnOrType) 51 | if not result: 52 | if isinstance(columnOrType, DataType): 53 | dataType = columnOrType 54 | result = mindlakesdk.message.getDKbyName(self.__session) 55 | if not result: 56 | return result 57 | else: 58 | tableName, columnName = columnOrType.split('.') 59 | result = mindlakesdk.message.getDataTypeByName(self.__session, tableName, columnName) 60 | if not result: 61 | return result 62 | dataType = DataType(result.data) 63 | result = mindlakesdk.message.getDKbyName(self.__session, self.__session.walletAddress, tableName, columnName) 64 | # Temporary solution for MS not returning Error Code 65 | if result.code == 40010: 66 | # DK not found, create one 67 | result = mindlakesdk.keyhelper.genDK(self.__session, tableName, columnName) 68 | if not result: 69 | return result 70 | elif not result: 71 | return result 72 | else: 73 | pass 74 | encTypeNum = Cryptor.EncType['enc_' + dataType.name].value 75 | ctxid = result.data['ctxId'] 76 | dkCipher = result.data['encryptedDek'] 77 | dkID, dk = mindlakesdk.keyhelper.decryptDKb64(self.__session.mk, dkCipher) 78 | alg = result.data['algorithm'] 79 | result = (ctxid, encTypeNum, dk, alg, dataType) 80 | self.cryptParamsByColumn[columnOrType] = result 81 | self.cryptParamsByID[ctxid] = (dk, alg) 82 | return ResultType(0, None, result) 83 | 84 | 85 | def __encodeByDataType(data, dataType: DataType) -> bytes: 86 | if dataType == DataType.int4: 87 | result = struct.pack(" ResultType: 110 | if isinstance(cipher, str): 111 | data = bytes.fromhex(cipher[2:]) 112 | else: 113 | data = cipher 114 | header = Cryptor.__extractCryptoHeader(data) 115 | encTypeNum = Cryptor.__extractEncType(header) 116 | ctxId = Cryptor.__extractCtxId(header) 117 | result = self.__getDecryptParams(ctxId) 118 | if not result: 119 | return result 120 | dk, alg = result.data 121 | idx = (header[1] & 0x7) + 2 122 | if alg == 3: 123 | iv = data[idx:idx+16] 124 | cipherBlob = data[idx+16:] 125 | plainBlob = mindlakesdk.utils.aesDecrypt(dk, iv, cipherBlob) 126 | elif alg == 0: 127 | iv = data[idx:idx+12] 128 | idx += 12 129 | mac = data[idx:idx+16] 130 | idx += 16 131 | cipherBlob = data[idx:] 132 | plainBlob = mindlakesdk.utils.aesGCMDecrypt(dk, iv, cipherBlob, mac) 133 | else: 134 | return ResultType(60004, "Unsupported algorithm to decrypt") 135 | result = plainBlob[:-1] 136 | checkCode = plainBlob[-1:] 137 | checkCode2 = Cryptor.__genCheckCode(result, 1) 138 | if checkCode != checkCode2: 139 | return ResultType(60005, "Check code of cipher is not correct") 140 | result = Cryptor.__decodeByEncType(result, Cryptor.EncType(encTypeNum)) 141 | return ResultType(0, None, result) 142 | 143 | def __getDecryptParams(self, ctxId: int): 144 | params = self.cryptParamsByID.get(ctxId) 145 | if not params: 146 | result = mindlakesdk.message.getDKbyCid(self.__session, ctxId) 147 | if not result: 148 | return result 149 | if not result.data: 150 | return ResultType(60002, "Can't get data key") 151 | dkCipher = result.data['encryptedDek'] 152 | if not dkCipher: 153 | return ResultType(60002, "Can't get data key") 154 | try: 155 | dkID, dk = mindlakesdk.keyhelper.decryptDKb64(self.__session.mk, dkCipher) 156 | except: 157 | return ResultType(60003, "Can't handle data key") 158 | alg = result.data['algorithm'] 159 | if alg is None: 160 | return ResultType(60002, "Can't get data key") 161 | params = (dk, alg) 162 | self.cryptParamsByID[ctxId] = params 163 | return ResultType(0, None, params) 164 | 165 | def __decodeByEncType(data, encType: EncType): 166 | if encType == Cryptor.EncType.enc_int4: 167 | size = struct.calcsize('> 3 216 | return type_value 217 | 218 | def __extractCtxId(header) -> int: 219 | ctxIdLen = header[1] & 0x7 220 | assert len(header) == ctxIdLen + 2 221 | ctxId = 0 222 | for i in range(ctxIdLen): 223 | index = len(header) - 1 - i 224 | ctxId = ctxId << 8 | (header[index] & 0xFF) 225 | return ctxId 226 | 227 | def __genCryptoHeader(ctxid, encType): 228 | head = bytearray(bytes.fromhex('0000')) 229 | 230 | tmp_value = head[1] 231 | tmp_value = tmp_value & 0xFFFFFF07 232 | tmp_value = tmp_value | (encType << 3) 233 | head[1] = tmp_value 234 | 235 | tmp = ctxid 236 | while tmp != 0: 237 | head.append(tmp & 0xFF) 238 | tmp >>= 8 239 | 240 | ctxLen = len(head) - 2 241 | 242 | tmp_val = head[1] 243 | tmp_val = (tmp_val & 0xFFFFFFF8) | (ctxLen & 0x7) 244 | head[1] = tmp_val 245 | return bytes(head) 246 | 247 | def __genCheckCode(data: bytes, resultSize): 248 | tmpCode = bytearray(resultSize) 249 | for i in range(len(data)): 250 | n = i % resultSize 251 | tmpCode[n] ^= data[i] 252 | return bytes(tmpCode) 253 | -------------------------------------------------------------------------------- /tests/test_base.py: -------------------------------------------------------------------------------- 1 | #### set path to load source code #### 2 | import os 3 | import sys 4 | import inspect 5 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 6 | parentdir = os.path.dirname(currentdir) 7 | sys.path.insert(0, parentdir) 8 | import logging 9 | #logging.info("========== set path ===========") 10 | 11 | #### load MindLake from source code #### 12 | from mindlakesdk import MindLake 13 | #logging.info('MindLake Path: %s', mindlake.__path__) 14 | #logging.info("========== load MindLake ===========") 15 | 16 | #### load logging #### 17 | import logging 18 | #mindlake.utils.setLogging(logging.INFO) 19 | #logging.info("========== load logging ===========") 20 | 21 | #### load env #### 22 | import env 23 | #logging.info("========== load env ===========") 24 | 25 | import unittest 26 | from pprint import pprint 27 | import datetime 28 | 29 | logging.info("========== load test base ===========") 30 | 31 | # import colorlog 32 | import sys 33 | import os 34 | 35 | def setLogging(logginglevel): 36 | root = logging.getLogger() 37 | if len(root.handlers): ### ensure only one logging handler is added 38 | logging.debug("multiple logging exists") 39 | root.handlers.clear() 40 | root.setLevel(logginglevel) 41 | 42 | format = '%(asctime)s|%(levelname)-8s |: %(message)s' 43 | date_format = '%Y-%m-%d %H:%M:%S' 44 | if 'colorlog' in sys.modules and os.isatty(2): 45 | cformat = '%(log_color)s' + format 46 | f = colorlog.ColoredFormatter(cformat, date_format, 47 | log_colors = { 'DEBUG' : 'cyan', 'INFO' : 'green', 48 | 'WARNING' : 'bold_red', 'ERROR': 'bold_red', 49 | 'CRITICAL': 'bold_red' }) 50 | else: 51 | f = logging.Formatter(format, date_format) 52 | ch = logging.StreamHandler() 53 | ch.setFormatter(f) 54 | root.addHandler(ch) 55 | 56 | def print_test_functions(): 57 | logging.info(('******* test | %s *******'%(inspect.stack()[1][3]))) 58 | 59 | ################################################ 60 | def drop_all_cocoon_and_table(mindlake: MindLake): 61 | # additional test is to drop all cocoon and tables 62 | logging.info("==== test to drop all cocoon and table | start =====") 63 | 64 | result = mindlake.datalake.listCocoon() 65 | logging.info('listCocoon: %s %s'%(result.code, result.message)) 66 | logging.debug(result.data) 67 | 68 | cocoonToDrop = {} 69 | cocoons = result.data 70 | if cocoons == None: 71 | cocoons = [] 72 | for cocoon in cocoons: 73 | cocoonName = cocoon['cocoonName'] 74 | cocoonToDrop[cocoonName] = {} 75 | result = mindlake.datalake.listTablesByCocoon(cocoonName) 76 | logging.info('listTablesByCocoon: %s %s %s'%(cocoonName, result.code, result.message)) 77 | tables = result.data 78 | logging.debug(tables) 79 | for table in tables: 80 | cocoonToDrop[cocoonName][table] = {} 81 | 82 | logging.info("===== show cocoon and tables to drop =====") 83 | logging.debug(cocoonToDrop) 84 | 85 | cocoonDropTableNonEmpty = {} 86 | cocoonDropped = {} 87 | tableDropped = {} 88 | 89 | for cocoonName in cocoonToDrop: 90 | if len(cocoonToDrop[cocoonName]) > 0: 91 | result = mindlake.datalake.dropCocoon(cocoonName) 92 | logging.info('dropCocoon: %s %s %s'%(cocoonName, result.code, result.message)) 93 | logging.debug(result.data) 94 | if result: 95 | cocoonDropTableNonEmpty[cocoonName] = {} 96 | 97 | for tableName in cocoonToDrop[cocoonName]: 98 | result = mindlake.datalake.dropTable(tableName) 99 | logging.info('dropTable: %s %s %s'%(tableName, result.code, result.message)) 100 | logging.debug(result.data) 101 | if not result: 102 | tableDropped[table] = {} 103 | 104 | result = mindlake.datalake.dropCocoon(cocoonName) 105 | 106 | logging.info('dropCocoon: %s %s'%(cocoonName, result.code)) 107 | logging.debug(result.data) 108 | if result: 109 | cocoonDropped[cocoonName] = {} 110 | 111 | logging.info("==== all and cacoon are deleted ====") 112 | 113 | 114 | 115 | ################################################ 116 | def drop_test_table(mindlake: MindLake, tableName): 117 | result = mindlake.datalake.dropTable(tableName) 118 | logging.info('dropTable: %s %s %s'%(tableName, result.code, result.message)) 119 | logging.debug(result.data) 120 | return result.code, result.data 121 | 122 | 123 | 124 | ################################################ 125 | def create_test_table_nonencrypted(mindlake: MindLake, tableName): 126 | columns = [] 127 | columns.append(mindlake.datalake.Column('mid', mindlake.DataType.text, False)) 128 | columns.append(mindlake.datalake.Column('name', mindlake.DataType.text, False)) 129 | columns.append(mindlake.datalake.Column('age', mindlake.DataType.text, False)) 130 | logging.debug(columns) 131 | result = mindlake.datalake.createTable(tableName, columns) 132 | logging.info('createTable: %s %s %s'%(tableName, result.code, result.message)) 133 | logging.debug(result.data) 134 | return result.code 135 | 136 | def create_test_table_nonencrypted_unique(mindlake: MindLake, tableName): 137 | columns = [] 138 | columns.append(mindlake.datalake.Column('mid', mindlake.DataType.text, False)) 139 | columns.append(mindlake.datalake.Column('name', mindlake.DataType.text, False)) 140 | columns.append(mindlake.datalake.Column('age', mindlake.DataType.text, False)) 141 | logging.debug(columns) 142 | primarykeys = ['mid', 'name'] 143 | logging.debug(primarykeys) 144 | result = mindlake.datalake.createTable(tableName, columns, primarykeys) 145 | logging.info('createTable: %s %s %s'%(tableName, result.code, result.message)) 146 | logging.debug(result.data) 147 | return result.code 148 | 149 | def create_test_table_encrypted(mindlake: MindLake, tableName): 150 | columns = [] 151 | columns.append(mindlake.datalake.Column('dataint4', mindlake.DataType.int4, True)) 152 | columns.append(mindlake.datalake.Column('dataint8', mindlake.DataType.int8, True)) 153 | columns.append(mindlake.datalake.Column('datafloat4', mindlake.DataType.float4, True)) 154 | columns.append(mindlake.datalake.Column('datafloat8', mindlake.DataType.float8, True)) 155 | columns.append(mindlake.datalake.Column('datadecimal', mindlake.DataType.decimal, True)) 156 | columns.append(mindlake.datalake.Column('datatext', mindlake.DataType.text, True)) 157 | columns.append(mindlake.datalake.Column('datatimestamp', mindlake.DataType.timestamp, True)) 158 | logging.debug(columns) 159 | result = mindlake.datalake.createTable(tableName, columns) 160 | logging.info('createTable: %s %s %s'%(tableName, result.code, result.message)) 161 | logging.debug(result.data) 162 | return result.code 163 | 164 | def create_test_table_encrypted_unique(mindlake: MindLake, tableName): 165 | columns = [] 166 | columns.append(mindlake.datalake.Column('dataint4', mindlake.DataType.int4, True)) 167 | columns.append(mindlake.datalake.Column('dataint8', mindlake.DataType.int8, True)) 168 | columns.append(mindlake.datalake.Column('datafloat4', mindlake.DataType.float4, True)) 169 | columns.append(mindlake.datalake.Column('datafloat8', mindlake.DataType.float8, True)) 170 | columns.append(mindlake.datalake.Column('datadecimal', mindlake.DataType.decimal, True)) 171 | columns.append(mindlake.datalake.Column('datatext', mindlake.DataType.text, True)) 172 | columns.append(mindlake.datalake.Column('datatimestamp', mindlake.DataType.timestamp, True)) 173 | logging.debug(columns) 174 | primarykeys = ['dataint4'] 175 | logging.debug(primarykeys) 176 | result = mindlake.datalake.createTable(tableName, columns, primarykeys) 177 | logging.info('createTable: %s %s %s'%(tableName, result.code, result.message)) 178 | logging.debug(result.data) 179 | return result.code 180 | 181 | 182 | 183 | 184 | ################################################ 185 | def insert_test_table_nonencrypted(mindlake: MindLake, tableName): 186 | # insert one raw 187 | sql = f"""INSERT INTO "{tableName}" (mid, name, age) VALUES ('a1', 'b1', 'c1') RETURNING *""" 188 | logging.debug(sql) 189 | q = mindlake.datalake.query(sql) 190 | logging.info('Query: Code: %s %s %s', q.code, q.message, sql) 191 | assert q, q.message 192 | logging.debug(q.data) 193 | 194 | # insert second raw 195 | sql = f"""INSERT INTO "{tableName}" (mid, name, age) VALUES ('a1', 'b1', 'c1') RETURNING *""" 196 | logging.debug(sql) 197 | q = mindlake.datalake.query(sql) 198 | logging.info('Query: Code: %s %s %s', q.code, q.message, sql) 199 | logging.debug(q.data) 200 | 201 | #print("===== select * =====") 202 | sql = f"""SELECT * FROM "{tableName}" """ 203 | logging.debug(sql) 204 | q = mindlake.datalake.query(sql) 205 | logging.info('Query: Code: %s %s %s', q.code, q.message, sql) 206 | logging.debug(q.data) 207 | count_select_all = len(q.data['data']) 208 | 209 | return count_select_all 210 | 211 | 212 | def insert_test_table_encrypted(mindlake: MindLake, tableName, data): 213 | encryptedData = {} 214 | for k, v in data.items(): 215 | logging.info(f"===== encrypt {k} in column =====") 216 | currentColumn = 'data' + k 217 | q = mindlake.cryptor.encrypt(v, f'{tableName}.{currentColumn}') 218 | logging.debug('Code: %s %s %s', q.code, q.message, f'encrypt {k}') 219 | assert q and isinstance(q.data, str) and q.data[:2] == '\\x', 'encryption test failed !' 220 | encryptedData[currentColumn] = q.data 221 | 222 | sql = f"""INSERT INTO "{tableName}" 223 | VALUES ( 224 | '{encryptedData['dataint4']}', 225 | '{encryptedData['dataint8']}', 226 | '{encryptedData['datafloat4']}', 227 | '{encryptedData['datafloat8']}', 228 | '{encryptedData['datadecimal']}', 229 | '{encryptedData['datatext']}', 230 | '{encryptedData['datatimestamp']}')""" 231 | logging.debug(sql) 232 | q = mindlake.datalake.query(sql) 233 | logging.debug('Code: %s %s %s', q.code, q.message, 'INSERT') 234 | return q 235 | 236 | 237 | ################################################ 238 | def update_test_table_nonencrypted(mindlake: MindLake, tableName): 239 | # insert one raw 240 | sql = f"""UPDATE "{tableName}" SET name = 'b11', age = 'c11' WHERE mid = 'a1' RETURNING *""" 241 | logging.debug(sql) 242 | q = mindlake.datalake.query(sql) 243 | logging.info('Query Code: %s %s %s', q.code, q.message, sql) 244 | logging.debug(q.data) 245 | 246 | logging.info("===== select * =====") 247 | sql = f"SELECT * FROM {tableName} WHERE mid = 'a1'" 248 | q = mindlake.datalake.query(sql) 249 | logging.info('Query Code: %s %s %s', q.code, q.message, sql) 250 | assert q, q.message 251 | logging.debug(q.data) 252 | return q.data['data'] 253 | 254 | def update_test_table_encrypted(mindlake: MindLake, tableName, data, index): 255 | encryptedData = {} 256 | for k, v in data.items(): 257 | logging.info(f"===== encrypt {k} in column =====") 258 | currentColumn = 'data' + k 259 | q = mindlake.cryptor.encrypt(v, f'{tableName}.{currentColumn}') 260 | logging.debug('Code: %s %s %s', q.code, q.message, f'encrypt {k}') 261 | assert q and isinstance(q.data, str) and q.data[:2] == '\\x', 'encryption test failed !' 262 | encryptedData[currentColumn] = q.data 263 | 264 | q = mindlake.cryptor.encrypt(index, mindlake.DataType.int4) 265 | assert q, q.message 266 | encryptedIndex = q.data 267 | 268 | sql = f"""UPDATE {tableName} 269 | SET 270 | dataint8 = '{encryptedData['dataint8']}', 271 | datafloat4 = '{encryptedData['datafloat4']}', 272 | datafloat8 = '{encryptedData['datafloat8']}', 273 | datadecimal = '{encryptedData['datadecimal']}', 274 | datatext = '{encryptedData['datatext']}', 275 | datatimestamp = '{encryptedData['datatimestamp']}' 276 | WHERE 277 | dataint4 = '{encryptedIndex}' 278 | RETURNING *""" 279 | logging.debug(sql) 280 | q = mindlake.datalake.query(sql) 281 | logging.info('Query Code: %s %s %s', q.code, q.message, sql) 282 | assert q, q.message 283 | return q 284 | 285 | 286 | ################################################ 287 | def link_table_to_cocoon_test(mindlake: MindLake, tableName, cocoonName): 288 | result = mindlake.datalake.linkTableToCocoon(tableName, cocoonName) 289 | logging.info('linkTableToCocoon: %s->%s %s %s'%(tableName, cocoonName, result.code, result.message)) 290 | logging.debug(result.data) 291 | return result.code 292 | 293 | def create_test_cocoon(mindlake: MindLake, cocoonName): 294 | result = mindlake.datalake.createCocoon(cocoonName) 295 | logging.info('createCocoon: %s %s %s'%(cocoonName, result.code, result.message)) 296 | logging.debug(result.data) 297 | return result.code 298 | 299 | def drop_test_cocoon(mindlake: MindLake, cocoonName): 300 | result = mindlake.datalake.dropCocoon(cocoonName) 301 | logging.info('dropCocoon: %s %s %s'%(cocoonName, result.code, result.message)) 302 | logging.debug(result.data) 303 | return result.code, result.data 304 | 305 | 306 | ################################################ 307 | def check_coocoon_exists(result, cocoonName): 308 | found = False 309 | if result == None: 310 | return found 311 | for r in result: 312 | if r['cocoonName'] == cocoonName: 313 | found = True 314 | break 315 | #print(found) 316 | return found 317 | 318 | def check_table_exists(result, tableName): 319 | return tableName in result 320 | 321 | 322 | def check_grantee_exists(result, walletAddress): 323 | return walletAddress in result 324 | 325 | 326 | def check_column_exists(result, columnName): 327 | return columnName in result 328 | 329 | 330 | --------------------------------------------------------------------------------