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

  1. Register an account on privsci.com
  2. Check email for verification link
  3. 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]

Options

Option Type Description
--default_db Flag If set, configures the client to use the default local client-side database storage.
--org String Sets the name of the organization in the configuration.
--domain String Sets the target domain (i.e. molecule).
--rep String Defines the data representation format preference (i.e. smiles).

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]

Options

Option Type Description
--reset Flag Destructive Action: Drops all existing tables and data, then re-initializes a fresh database. This requires an interactive user confirmation before proceeding unless the input stream is forced.

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]

Arguments

Argument Description
PERMISSIONS A 3-character string representing flags.
Pos 1: 'C' (Create) or 'x'
Pos 2: 'S' (Sign) or 'x'
Pos 3: 'E' (Export) or 'x'
Example: "CSx" enables Create and Sign, but disables Export.

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]

Options

Option Type Description
--activate Flag If set, the generated key is immediately saved to the local configuration file as the active api_key.

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:

  1. Canonicalization: The input (i.e. a SMILES string) is passed to a domain-specific handler (like rdkit for molecules) to convert it into a standard, reproducible format.
  2. 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.
  3. 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 LedgerController appends 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

Warning: Managing Your Salts

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)

Parameters

Parameter Type Required Description
structure_list List[str] Yes The list of raw inputs (i.e. SMILES strings) you wish to verify.
org_name str No The name of the group that the assets are associated with. Defaults to auto-filled by config.
api_key str No The API key of the user checking the structure and/or action existence. Defaults to auto-filled by config.
domain str No The domain of the structure type (i.e. molecule). Defaults to auto-filled by config.
representation str No The representation of the structure type (i.e. SMILES string). Defaults to auto-filled by config.

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

Dependency on Local State

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)

Parameters

Parameter Type Required Description
salt_list List[str] No The salts associated with the structure (i.e. SMILES strings) and actions submitted for verification. Auto-filled if using default client side storage.
structure_list List[str] Yes The list of raw inputs (i.e. SMILES strings) you wish to verify.
action_list List[str] No If provided, the client verifies if this specific action (i.e. "tested", "approved") was performed on the structure. Defaults to None (verifies the structure itself). Auto-filled if using default client side storage.
org_name str No The name of the group that the assets are associated with. Defaults to auto-filled by config.
api_key str No The API key of the user checking the structure and/or action existence. Defaults to auto-filled by config.
domain str No The domain of the structure type (i.e. molecule). Defaults to auto-filled by config.
representation str No The representation of the structure type (i.e. SMILES string). Defaults to auto-filled by config.

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

Warning: Managing Your Salts

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)

Parameters

Parameter Type Required Description
salt_list List[str] No The salts associated with the structure (i.e. SMILES strings) that will be tagged with actions and submitted to the Merkle Mountain Range. Auto-filled if using default client side storage.
structure_list List[str] Yes The list of raw inputs (i.e. SMILES strings) you wish to verify that will be tagged with actions and submitted to the Merkle Mountain Range.
action_list List[str] Yes The list of actions (i.e. "reviewed", "tested molecule x and got result y...", "approved for further processing") performed on the structure with the same index in the structure_list.
org_name str No The name of the group that the assets are associated with. Defaults to auto-filled by config.
api_key str No The API key of the user checking the structure and/or action existence. Defaults to auto-filled by config.
domain str No The domain of the structure type (i.e. molecule). Defaults to auto-filled by config.
representation str No The representation of the structure type (i.e. SMILES string). Defaults to auto-filled by config.

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)

Parameters

Parameter Type Required Description
leaf_index_list List[int] Yes A list of ledger sequence numbers (indices) for which you require inclusion proofs.
old_sth Dict Yes The "Signed Tree Head" from a previous trusted state. The client uses the tree_size from this object to request a consistency proof starting from that point in history.
org_name String No The target organization. Defaults to local config.
api_key String No The valid API key. Defaults to local config.

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
)

Parameters

Parameter Type Required Description
salt String Yes The raw data input (i.e. SMILES) you are verifying. The function canonicalizes this locally before hashing.
structure Dict Yes The inclusion proof object (containing the `path` and `peaks`) retrieved via export().
action String / Bytes Yes The trusted Root Hash (from a Signed Tree Head) against which you are verifying the proof.
proof String No If provided, verifies the existence of a specific action performed on the structure. If None, verifies the structure itself.
root String No The salt used during the original creation. If None, the client attempts to fetch it from the local DB.
org_name String No The salt used during the original creation. If None, the client attempts to fetch it from the local DB.
domain String No The salt used during the original creation. If None, the client attempts to fetch it from the local DB.
representation String No The salt used during the original creation. If None, the client attempts to fetch it from the local DB.

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
)

Parameters

Parameter Type Required Description
old_sth Dict Yes The Signed Tree Head (containing tree_size and root_hash) from the past.
new_sth Dict Yes The Signed Tree Head from the present.
proof Dict Yes The consistency proof object containing old_peaks, path, and new_peaks.

Return Value

Both functions return a Boolean:

  • True: The cryptographic proof is valid.
  • False: The proof is invalid (indicating data corruption or server tampering).