Skip to main content

solidus-network

Python SDK for Solidus Networkdid:solidus derivation, identifier validation, W3C verification-method encoding, strict Ed25519, and BBS+ selective disclosure.

Status: not on PyPI yet, and the remaining step is not code. The distribution is solidus-network at 0.1.0, wheels build for five platforms, and publishing goes through PyPI Trusted Publishing over OIDC, so no API token exists anywhere. What is outstanding is the one-time pending-publisher form in the PyPI web interface; after that, a v* tag publishes.

0.1.0 can be uploaded exactly once and a burned version number cannot be reused, which is why nothing is tagged until that form is filled in.

What it does, and what it does not

Ships: address and DID derivation · identifier validation (SPEC v0.2.0 §4.1) · publicKeyMultibase encoding and decoding · strict Ed25519 verification · BBS+ signature verification and selective-disclosure proofs.

Not in the first version:

  • Transaction signing. A write path means key handling, and in Python that means people pasting private keys into notebooks. The read path ships well first.
  • DID resolution over the network. The encoding and validation this package does are the parts implementations get wrong. An HTTP client is not.
  • SD-JWT VC issuance.

Install

Not yet. When it publishes:

pip install solidus-network

Installing from source — read this before you try

A standalone clone of this repository cannot currently build the native module. The BBS+ binding depends on solidus-crypto, which is not on crates.io, so Cargo.toml carries a path that only resolves inside the Solidus monorepo. pip install git+https://… will fail at the Rust build.

This is stated here rather than left for you to discover, and it is a real limitation, not a formality. Two things lift it, in this order:

  1. Publish solidus-crypto to crates.io, so the dependency resolves for anyone.
  2. A CI wheel matrix (linux/macos/windows × cp39–cp314), so pip install solidus-network needs no Rust toolchain at all. An sdist alone would force one on every user.

Neither has happened yet. Until then this repository is readable, reviewable and runnable inside the monorepo — and the pure-Python half (derivation, DIDs, multibase, strict Ed25519) has no native dependency and works from a clone today.

⚠ A git dependency on the public protocol repository would not fix this. That copy of solidus-crypto predates the feature gating, so blst is not optional there and building against it would drag a C BLS toolchain into every wheel — the exact cost the gating removed.

Usage

Every example below is executed by the test suite — pytest --doctest-glob='*.md' README.md. A README that stops being true is a test failure. That is not decoration: an outside developer found four blocks in our TypeScript README that did not compile against the published packages, and this is the response.

Derive an identity from a mnemonic

>>> from solidus_network import seed_from_mnemonic, identity_key
>>> seed = seed_from_mnemonic(" ".join(["abandon"] * 23 + ["art"]))
>>> key = identity_key(seed)
>>> key.did("testnet")
'did:solidus:testnet:3tBoVe6XRtirzr8SdRotGgbkuEQN'

The identity key is seed64[:32] — a raw slice, deliberately not routed through the HKDF hierarchy below. That asymmetry is load-bearing and frozen by the conformance vectors.

Derive a different key for every verifier

A wallet gives each verifier its own key, so two verifiers holding the same user cannot correlate them.

>>> from solidus_network import pairwise_key
>>> a = pairwise_key(seed, "rp-a.example.com")
>>> b = pairwise_key(seed, "rp-b.example.com")
>>> a.identifier == b.identifier
False
>>> a.did()
'did:solidus:testnet:3ThmUf3VBefVuaQSBGzC1fcP5iKS'

Validate a DID before you resolve it

This is what separates invalidDid from notFound — "no such DID" implies the identifier could have existed.

>>> from solidus_network import is_valid_did
>>> is_valid_did("did:solidus:testnet:3tBoVe6XRtirzr8SdRotGgbkuEQN")
True
>>> is_valid_did("did:solidus:devnet:3tBoVe6XRtirzr8SdRotGgbkuEQN")  # did-syntax:invalid-on-purpose
False
>>> is_valid_did("did:solidus:testnet:0OIl000000000000000000")  # did-syntax:invalid-on-purpose
False

Read a key out of a DID Document, and verify with it

>>> from solidus_network import decode_public_key_multibase, ed25519
>>> import nacl.signing
>>> signer = nacl.signing.SigningKey(b"\x07" * 32)
>>> from solidus_network import public_key_multibase
>>> verification_method = {
...     "type": "Ed25519VerificationKey2020",
...     "publicKeyMultibase": public_key_multibase(bytes(signer.verify_key)),
... }
>>> signed = signer.sign(b"a credential")
>>> ed25519.verify_multibase(
...     verification_method["publicKeyMultibase"], b"a credential", signed.signature)
True

Verification is strict: it rejects small-order keys and non-canonical encodings. The vacuous identity equation — an all-zeros key with an all-zeros signature, which satisfies ZIP-215 for any message — does not verify here.

>>> ed25519.verify(bytes(32), b"anything at all", bytes(64))
False

Verify a BBS+ selective-disclosure proof

The verifier never sees the undisclosed claims and never sees the issuer's signature. It sees the proof, the issuer's public key, and the (index, message) pairs being asserted.

>>> import pytest
>>> _ = pytest.importorskip("solidus_network.solidus_network_native")  # skips without the wheel
>>> from solidus_network import bbs
>>> sk = bytes.fromhex(
...     "363ef9668e4e1cf86b5f2092c51f7c056d6841cec69920cc5d887f68c6cab6d1")
>>> bbs.public_key_hex(sk)[:32]
'9898c245f85011e9092e9a3d20ac204d'

(The importorskip line keeps this file runnable as a test in a clone without the compiled extension. Your own code needs only the from solidus_network import bbs.)

Full sign → prove → verify flows, including every negative case, are in tests/test_vectors.py against the published conformance suite.

API

module name what it is
solidus_network seed_from_mnemonic(mnemonic, passphrase="") BIP-39 → 64-byte seed, NFKD-normalised here
identity_key(seed64)DerivedKey seed64[:32], outside the HKDF tree
pairwise_key(seed64, verifier_id)DerivedKey HKDF-SHA512, one unlinkable key per verifier
DerivedKey.identifier · .did(network) base58 address · full did:solidus:…
solidus_network.did identifier_for(public_key) base58(BLAKE3-256(key)[:20])
did_for(public_key, network) the full DID string
public_key_multibase(public_key) z6Mk…, with the 0xed01 multicodec header
decode_public_key_multibase(mb) the inverse; also accepts pre-2026-08-07 headerless keys
is_valid_identifier(s) · is_valid_did(s) SPEC v0.2.0 §4.1 syntax
solidus_network.ed25519 verify(public_key, msg, sig) strict; returns False, never raises
verify_multibase(mb, msg, sig) the same, straight from a DID Document
solidus_network.bbs public_key_hex(secret_key) from key bytes, never from IKM
verify(sig_hex, pk_hex, header, messages) over the full message vector
create_proof(…) → proof hex holder side
verify_proof(…) verifier side

Conformance

test-vectors/ is published so third parties can check us rather than take our word. This package runs them itself, and prints its own scope:

8/12 vectors in scope. A green run does NOT mean 12/12. Out of scope:
  credential-bundle (3) — agent-identity message map — product layer, not the protocol surface
  did-tx-create (1) — transaction signing is out of v0.1.0 — read path first

Eight of twelve, stated openly, is the honest number. An unimplemented category fails this suite — it is never skipped. A runner that quietly passes what it does not understand reports full marks while checking a fraction, which is worse than having no runner: it manufactures confidence in exactly the artifact we ask outsiders to trust.

Ed25519 is strict, and the library choice is why

Verification must reject small-order public keys and non-canonical encodings, which is what gives strongly binding signatures — exclusive ownership, the property a verifiable credential exists to assert. ZIP-215 verification accepts both and does not.

This package depends on PyNaCl (libsodium) rather than cryptography (OpenSSL), whose Ed25519 is permissive about small-order keys. libsodium's exact behaviour varies by version, so it is asserted in tests/test_ed25519_strict.py rather than promised here — a test is the only version of this claim that stays true.

BBS+ is native, and here is what that costs

There is no usable native Python BBS+ implementation. Checked against PyPI on 2026-08-07: bbs is an empty 0.0.1 placeholder, ursa is gone, and blspy/py_ecc are BLS primitives — building on them would mean implementing draft-irtf-cfrg-bbs-signatures in Python.

So BBS+ binds the Rust crate via PyO3, which buys byte-parity with the chain and costs a per-platform wheel. Everything else is pure Python and ships in the same wheel at no native cost. The binding is feature-gated to bbs only, so no C BLS toolchain enters the build.

This is why pip install solidus-network is not yet a promise the repo can keep. An sdist alone forces every user to have a Rust toolchain; wheels have to be built per platform first. That matrix now exists (.github/workflows/python-wheels.yml in the monorepo) and produces five wheels rather than thirty: pyo3's abi3-py39 makes one wheel per platform serve every CPython from 3.9 up. Measured, not assumed — a wheel built on CPython 3.14 was installed on 3.9, 3.11 and 3.13 and reproduced the frozen BBS+ public key and DID on all three.

Development

Without a Rust toolchain — runs everything except the four BBS+ vectors, and says so:

git clone --recurse-submodules https://github.com/solidusnetwork/solidus-py.git
cd solidus-py
uv venv .venv && . .venv/bin/activate
uv pip install pytest blake3 pynacl
PYTHONPATH=python pytest

test-vectors/ is a submodule pointing at solidus-test-vectors — the same repository we ask third parties to run against their own implementations. It is a pointer, not a copy, so the suite here cannot quietly diverge from the one we publish.

With a Rust toolchain, inside the monorepo, for the full 8/12:

uv pip install maturin
maturin develop
pytest

SOLIDUS_REQUIRE_NATIVE=1 turns a missing extension from a report into an error. CI sets it, so a wheel that shipped without its native half cannot go green.

pytest runs the conformance vectors, every docstring example, and every example in this file.

Before believing the suite, break something and watch it fail. Dropping the multicodec header from public_key_multibase, or making verify() return True, each takes down the specific vectors it should and nothing else — and two of the four failures we seeded were caught by the doctests independently of the vector runner.

Licence

Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solidus_network-0.1.0.tar.gz (64.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

solidus_network-0.1.0-cp39-abi3-win_amd64.whl (260.6 kB view details)

Uploaded CPython 3.9+Windows x86-64

solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (420.1 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (419.3 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl (371.2 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl (372.2 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file solidus_network-0.1.0.tar.gz.

File metadata

  • Download URL: solidus_network-0.1.0.tar.gz
  • Upload date:
  • Size: 64.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for solidus_network-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a545ea56b04083a766372cac6cccee16eea19a2198214bd516f6861239c41b60
MD5 5c71a804abfecf0d6a09175b263d5641
BLAKE2b-256 03ba8687c45c3a3eb3ceffc167bbca12a58eb503a642153933bd1bb44abaa183

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0.tar.gz:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solidus_network-0.1.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for solidus_network-0.1.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 fcfc3b565c82bff09b57b120e9b8d906f339e7f79189521c9a15791f209fdf85
MD5 cfa8781432371534d57c0f532ba7b427
BLAKE2b-256 d41201bd73f0d772668415e02b28ff645497e9c49756684f87170feb5f3737e9

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0-cp39-abi3-win_amd64.whl:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1e03639583e03c728fc48a9c6d0edf2ed6db4ec44a0433c1a83505b43a07d203
MD5 b66b105ae32397e7f2226941f5d16f55
BLAKE2b-256 b57ae8e528d3a7dfdd6dc0eed7905f4d2f4d42c9d9db608ab8d228265097f156

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 81583302570894ccbf05313070bd91e5ac9bace39566b87dae6c28c63d905845
MD5 3375d2a352b89be6754052915a829105
BLAKE2b-256 e0bd67173d33fe4b8e04ab6d42ba7afc4b162dfaaf229d4a43aa2888c1d6cff9

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9ab8a85e118c7836136e67d912f78b52169e41fb1b49e6292737347516e63ff7
MD5 d3c3f090011af7764fe1c565ebe92f38
BLAKE2b-256 bb84b3ab9533bd6db8bcd9af99a043c80a050e2682765872de42b82f26586ed9

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 0c249cf7430865ffc5ca53fc96120f281f5fd01d66d82084f91d265ca055fbb7
MD5 2b1ce2decafa4a3054b0e9fe065594bc
BLAKE2b-256 c858164d3a91ec86ef1feed0ec5cf1f829b2dc161d92990f1d5b786fea4bef99

See more details on using hashes here.

Provenance

The following attestation bundles were made for solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on solidusnetwork/solidus-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page