Installation
The PrivSci SDK is available for Python. It handles canonicalization and cryptographic operations locally before interacting with the ledger.
pip install privsci
Account Setup
Registration
- Register an account on privsci.com
- Check email for verification link
- Verify account
Log In
privsci accounts login [email]
Config Management
config set
Updates the local configuration file. Use this command to define organization details, target domains, or toggle storage preferences. Multiple options can be passed in a single execution.
Usage
cli config set [OPTIONS]
config init
Initializes the local client-side SQLite database connection. This command ensures the database file exists and tables are created based on the current configuration schema.
Usage
cli config init [OPTIONS]
Group Management
Group Lifecycle
groups create
Initializes a new group with the specified name. The creator automatically becomes the owner.
cli groups create [GROUP]
groups shut
Permanently shuts down or deletes an existing group. Only the owner can perform this action.
cli groups shut [GROUP]
groups transfer
Transfers ownership of the group to another existing member.
cli groups transfer [GROUP] [NEW_OWNER]
Group Membership
groups invite
Invites a user to the group. Attaches invitation lifetime duration.
cli groups invite [GROUP] [MEMBER_EMAIL] [DURATION]
groups join
Accepts an invitation to join a specific group.
cli groups join [GROUP]
groups leave
Removes the current user from the specified group.
cli groups leave [GROUP]
groups revoke
Forcefully removes a member from the group. Requires admin or owner privileges.
cli groups revoke [GROUP] [MEMBER_EMAIL]
Group Roles
groups promote / demote
Elevates a member to an admin role or demotes them back to a standard member.
cli groups promote [GROUP] [MEMBER_EMAIL]
cli groups demote [GROUP] [MEMBER_EMAIL]
groups adjust_permission
Fine-tunes specific capability flags for a group member.
cli groups adjust_permission [GROUP] [MEMBER_EMAIL] [PERMISSIONS]
Group Info
groups list
Displays a table of all groups the current user owns or is a member of, including their permissions in those groups.
cli groups list
groups members
Lists all members within a specific group, showing their email, role, and specific permission flags.
cli groups members [GROUP]
groups permissions
Returns the current user's specific permission string (i.e. 'CSE') for the target group.
cli groups permissions [GROUP]
Key Management
Key Lifecycle
keys generate
Generates a new API key on the server. The key is returned in the response. You can optionally configure your local client to use this new key immediately.
cli keys generate [KEY_NAME] [OPTIONS]
keys revoke
Permanently invalidates an existing API key. Once revoked, the key can no longer be used for authentication.
cli keys revoke [API_KEY]
Key Configuration
keys switch
Updates the local client configuration to use a specific API key. Use this to toggle between different keys without regenerating them.
cli keys switch [API_KEY]
create()
The create() function is the entry point for anchoring scientific data into the PrivSci ledger.
It is designed with a privacy-first architecture: sensitive data never leaves your local machine,
and the server acts strictly as a cryptographic notary.
1. Privacy & Architecture
When you run create(), your raw data (i.e. proprietary chemical structures) undergoes
local processing before any network request is made. The system relies on a "Commitment Scheme"
where the server receives only a cryptographic fingerprint of your data, never the data itself.
- Client-Side: Canonicalization, Salting, and Hashing.
- In-Transit: Only the SHA-256 hash is transmitted.
- Server-Side: The hash is stamped into a Merkle Mountain Range, proving existence without revealing content.
2. Client-Side Process
The client performs three critical steps to ensure data consistency and security:
-
Canonicalization: The input (i.e. a SMILES string) is passed to a domain-specific handler
(like
rdkitfor molecules) to convert it into a standard, reproducible format. - Salting: A cryptographically secure random salt (16 bytes) is generated for each entry. This prevents "rainbow table" attacks, ensuring that even if two users anchor the same molecule, their resulting hashes are unique and indistinguishable to the server.
-
Hashing: The combined string (
salt:canonical_structure) is hashed using SHA-256. This hash is the only piece of information sent to the API.
3. Server-Side Process
The server receives the list of hashes and performs the following:
- Validation: Verifies the user has valid permissions and that the request is within weekly rate limits.
-
Inclusion: The
LedgerControllerappends the hashes to the organization's Merkle Mountain Range. - Receipt Generation: The server returns a cryptographic receipt containing the new STH and a "Consistency Proof," which mathematically proves that the new tree includes the previous tree's history (i.e., the ledger has not been tampered with).
4. Critical: Data Management
Because the server does not know your raw data or your salts, it cannot help you recover them if lost.
To prove later that a specific molecule corresponds to a specific hash on the ledger, you must possess the exact Salt used during creation.
Using the Default DB (Recommended):
If you have configured and initialized the client with --default_db, the client automatically handles storage and accessing the
raw_structure, salt, canonical_form, and the returned receipt
in your local SQLite database.
Using Custom Storage:
If you opt out of the default database, the create() function returns a receipt containing
proof of the transaction STH, the generated salts, and hashes. You must store these securely in your own infrastructure.
Without the salt, the proof of existence is mathematically unverifiable.
5. Usage & Arguments
from privsci_client import create # Define your sensitive data aspirin = "O=C(C)Oc1ccccc1C(=O)O" benzene = "c1ccccc1" structure_list = [aspirin, benzene] # Establish Proof of Existence # This calculates the salt and hash locally before sending only the hash to the ledger. create_receipt = create(structure_list=structure_list) print("\nAspirin and Benzene creation") pprint.pprint(create_receipt)
Return Values
Aspirin and Benzene creation {'input_list': [{'can_structure': 'CC(=O)Oc1ccccc1C(=O)O', 'hash_value': '84980bc4493a5b38df370069d8fd5147d485abc01f4c3e0151c7412d639071b7', 'raw_structure': 'O=C(C)Oc1ccccc1C(=O)O', 'salt': 'e2201383cff291e06a12d140230ecc4f'}, {'can_structure': 'c1ccccc1', 'hash_value': '6fa72ac85b7d3c90f9df2c6d61cf1a6569a255c1aa9cfdbf72853dc7480ed44a', 'raw_structure': 'c1ccccc1', 'salt': '467c605089167b16b8c83af48f195438'}], 'receipt': {'consistency_proof': {'new_peaks': [{'hash': 'dd6d3e612a00308b8abe344756b27e50c657e4e18f204c451ada0412018b53f9', 'level': 1}], 'old_peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}], 'path': []}, 'meta': {'count_added': 2, 'start_index': 16, 'timestamp': 1767128801696}, 'receipt_type': 'action', 'sth': {'root_hash': '9dcc32d1913f417809a0d9ffecbf9840c52634f3d37b532682c70ba28ddc4c00', 'signature': '66acae676ef319d23b5e55ff6c0ac56d4ec4e9b67dc90d5098048a454a0a83b81c9216017ddd4236e410993542bff4b6a286d8baadbea94f744cf6c197479d05', 'timestamp': 1767128801696, 'tree_size': 18}}}
check()
The check() function verifies if a specific asset (or an action performed on that asset) is anchored on the PrivSci ledger.
Crucially, it reconstructs the cryptographic hash locally—using your private salts—so the server only answers "Yes/No" to a hash, without ever knowing the underlying molecule or data you are querying.
1. Important Notes
The check() function relies heavily on the Salt. Because the server does not store your salts,
you cannot use check() on a machine that lacks the local database context (or the manual list of salts)
generated during the original create() call.
Action Verification:
When verifying an action (i.e. check(..., action_list=["reviewed"])), the client calculates a "commitment hash."
This means the server can verify that "Aspirin was reviewed" without knowing what the hash of the molecule structure OR action string means.
2. Usage & Arguments
This function requires the original raw data. It attempts to automatically retrieve the associated salt from your local database to reconstruct the hash.
from privsci_client import check
# Confirm the proof of existence of Aspirin and Benzene in the database
# Useful to retrieve the timestamp or sequence number later.
check_receipt_list = check(structure_list=structure_list)
print("\nAspirin and Benzene existence")
pprint.pprint(check_receipt_list)
# Confirm the 'reviewed' operation on aspirin
# Useful to retrieve the timestamp or sequence number later.
check_receipt_list = check(structure_list=[aspirin], action_list=["reviewed"])
print("\n\'review\' existence")
pprint.pprint(check_receipt_list)
Return Values
# Aspirin and Benzene existence {'receipt': [{'exists': True, 'hash_value': '84980bc4493a5b38df370069d8fd5147d485abc01f4c3e0151c7412d639071b7', 'sequence_number': 16, 'timestamp': '2025-12-30T13:06:41', 'type': 'proof'}, {'exists': True, 'hash_value': '6fa72ac85b7d3c90f9df2c6d61cf1a6569a255c1aa9cfdbf72853dc7480ed44a', 'sequence_number': 17, 'timestamp': '2025-12-30T13:06:41', 'type': 'proof'}], 'status': 'ok'} # Receipt for proof that Aspirin was marked as 'reviewed' {'receipt': [{'exists': True, 'hash_value': 'c1baabe3a2a5af4fef07afc99217aabe709c6edb3865da36b6d0dcd99208da28', 'sequence_number': 18, 'timestamp': '2025-12-30T13:06:42', 'type': 'signature'}], 'status': 'ok'}
sign()
The sign() function anchors a specific event or action to the ledger, linking it cryptographically to an existing asset.
This creates a new leaf in the Merkle Mountain Range representing the "Action Commitment" (a hash of the structure's identity combined with the action string).
1. Privacy & Architecture
See equivalent section in create().
2. Client-Side Process
See equivalent section in create().
3. Server-Side Process
See equivalent section in create().
4. Critical: Data Management
Because the server does not know your raw data or your salts, it cannot help you recover them if lost.
To prove later that a specific molecule corresponds to a specific hash on the ledger, you must possess the exact Salt used during creation.
5. Usage & Arguments
Usage Example
from privsci_client import sign
# Create a record that the Aspirin structure was 'reviewed'
review_receipt = sign(structure_list=[aspirin], action_list=["reviewed"])
print("\n\'reviewed\' receipt")
pprint.pprint(review_receipt)
Return Values
'reviewed' receipt {'receipt': {'consistency_proof': {'new_peaks': [{'hash': 'af4a64a8525944779e0ae6d31dad65d6d0896155006e05b438c3c64753e4d59b', 'level': 0}, {'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}], 'old_peaks': [{'hash': 'dd6d3e612a00308b8abe344756b27e50c657e4e18f204c451ada0412018b53f9', 'level': 1}, {'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}], 'path': []}, 'meta': {'count_added': 1, 'start_index': 18, 'timestamp': 1767128802092}, 'receipt_type': 'action', 'sth': {'root_hash': '8b1fb531658d20f30ef72c7633eddee77571bff6a1f0809cf0e9998d99a529ab', 'signature': 'a72ab41f5d17fe0e689f14d4db1bb6a0de5809a6f629131d9a24d596e4cbe570e26282fa05c92b96b5898339065cb2dcb62a58097e09bf901cd97867b29fb90a', 'timestamp': 1767128802092, 'tree_size': 19}}, 'status': 'ok'}
export()
The export() function generates and returns a comprehensive cryptographic audit packet from the server. This function fetches both Inclusion Proofs (verifying specific items exist in the ledger) and a Consistency Proof (verifying the ledger history has not been altered since the last trusted state). Once generated, the proof package can be used for local verification of structures and/or actions completely independent of the PrivSci server.
1. Critical: Data Management
Using the Default DB (Recommended):
If you have configured and initialized the client with --default_db, the client automatically handles storage and accessing the
all the necessary information for preparing and submitting proofs provided by an audit packet for verification.
Using Custom Storage:
If you opt out of the default database, you are responsible for managing the export() audit packet and building your own
proof preparation script for client side submission of inclusion and consistency proofs for verification.
2. Usage & Arguments
Usage Example
from privsci_client import export # Generate an Audit Packet for Aspirin print("\nExporting Proof Packet...") audit_packet = export(leaf_index_list=leaf_index_list, old_sth=old_sth)['proof_packet'] print("\nAudit Package:") pprint.pprint(audit_packet)
Return Value
Audit Package: {'meta': {'created_at_size': 18, 'current_size': 20}, 'proofs': {'consistency': {'new_peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}], 'old_peaks': [{'hash': 'dd6d3e612a00308b8abe344756b27e50c657e4e18f204c451ada0412018b53f9', 'level': 1}, {'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}], 'path': [{'direction': 'R', 'hash': '3f8c125e12435b6b7475bb49faefd5fe546fd03b04dcf2a588f274b90f321237'}]}, 'inclusion': [{'leaf_index': 16, 'proof': {'path': [{'direction': 'R', 'hash': 'a9f94bef81a46f082eefe35d97d13818432d9037f13d3b80d1429bc58e492a78'}, {'direction': 'R', 'hash': '3f8c125e12435b6b7475bb49faefd5fe546fd03b04dcf2a588f274b90f321237'}], 'peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}]}}, {'leaf_index': 17, 'proof': {'path': [{'direction': 'L', 'hash': 'def2a4431904cfbfafc582b093c619353f5b1104b823a54ad147704c85bfeb13'}, {'direction': 'R', 'hash': '3f8c125e12435b6b7475bb49faefd5fe546fd03b04dcf2a588f274b90f321237'}], 'peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}]}}, {'leaf_index': 18, 'proof': {'path': [{'direction': 'R', 'hash': '01ce79f664f44d0cccf5ce856e13a4262085d0ed0200175b0723909e95bc9b79'}, {'direction': 'L', 'hash': 'dd6d3e612a00308b8abe344756b27e50c657e4e18f204c451ada0412018b53f9'}], 'peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}]}}, {'leaf_index': 19, 'proof': {'path': [{'direction': 'L', 'hash': 'af4a64a8525944779e0ae6d31dad65d6d0896155006e05b438c3c64753e4d59b'}, {'direction': 'L', 'hash': 'dd6d3e612a00308b8abe344756b27e50c657e4e18f204c451ada0412018b53f9'}], 'peaks': [{'hash': '373587578565bf3371b90c425a094563b6f69e11f37fda7b86db86bb3fa1439e', 'level': 4}]}}]}, 'receipt_type': 'export', 'sth': {'root_hash': '5727b7d8179c94ebf336ff9b442ce5f1c79e28b21c11f4305de36926832cb184', 'signature': '762abeb9a611359fad88971a7d21dc55eecd3576b976b1e29440a3c21bc9b8d7341c5fffe67a6d9089dbc5189492dc8ebbf5cf7af3fad2c4253bc9216306840c', 'timestamp': 1767128802863, 'tree_size': 20}}
verify_*()
Client-Side Verification
These functions perform cryptographic validation entirely within your local environment. By running the Merkle Mountain Range logic locally, you ensure that the server cannot falsify data existence or alter the ledger's history without detection.
1. verify_inclusion()
Determines if a specific piece of data (Structure or Action) is part of the Merkle Tree defined by a given Root Hash. This function first reconstructs your private hash using your local salt, then traverses the provided Merkle path to see if it mathematically leads to the trusted Root.
Usage
from privsci_client import verify_inclusion verify_inclusion( structure=aspirin, proof=aspirin_inclusion_proof, root=root )
2. verify_consistency()
Validates the "Append-Only" property of the ledger. It proves that a newer version of the tree (defined by new_sth)
includes everything that was present in an older version of the tree (defined by old_sth), ensuring that history has not been rewritten.
Usage
from privsci_client import verify_consistency verify_consistency( old_sth=trusted_old_sth, new_sth=trusted_new_sth, proof=consistency_proof )
Return Value
Both functions return a Boolean:
True: The cryptographic proof is valid.False: The proof is invalid (indicating data corruption or server tampering).