solidus-network
Python SDK for Solidus Network — did: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:
- Publish
solidus-cryptoto crates.io, so the dependency resolves for anyone. - A CI wheel matrix (linux/macos/windows × cp39–cp314), so
pip install solidus-networkneeds 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
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a545ea56b04083a766372cac6cccee16eea19a2198214bd516f6861239c41b60
|
|
| MD5 |
5c71a804abfecf0d6a09175b263d5641
|
|
| BLAKE2b-256 |
03ba8687c45c3a3eb3ceffc167bbca12a58eb503a642153933bd1bb44abaa183
|
Provenance
The following attestation bundles were made for solidus_network-0.1.0.tar.gz:
Publisher:
release.yml on solidusnetwork/solidus-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0.tar.gz -
Subject digest:
a545ea56b04083a766372cac6cccee16eea19a2198214bd516f6861239c41b60 - Sigstore transparency entry: 2684888913
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type:
File details
Details for the file solidus_network-0.1.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: solidus_network-0.1.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 260.6 kB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcfc3b565c82bff09b57b120e9b8d906f339e7f79189521c9a15791f209fdf85
|
|
| MD5 |
cfa8781432371534d57c0f532ba7b427
|
|
| BLAKE2b-256 |
d41201bd73f0d772668415e02b28ff645497e9c49756684f87170feb5f3737e9
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0-cp39-abi3-win_amd64.whl -
Subject digest:
fcfc3b565c82bff09b57b120e9b8d906f339e7f79189521c9a15791f209fdf85 - Sigstore transparency entry: 2684890320
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type:
File details
Details for the file solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 420.1 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e03639583e03c728fc48a9c6d0edf2ed6db4ec44a0433c1a83505b43a07d203
|
|
| MD5 |
b66b105ae32397e7f2226941f5d16f55
|
|
| BLAKE2b-256 |
b57ae8e528d3a7dfdd6dc0eed7905f4d2f4d42c9d9db608ab8d228265097f156
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
1e03639583e03c728fc48a9c6d0edf2ed6db4ec44a0433c1a83505b43a07d203 - Sigstore transparency entry: 2684889897
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type:
File details
Details for the file solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 419.3 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81583302570894ccbf05313070bd91e5ac9bace39566b87dae6c28c63d905845
|
|
| MD5 |
3375d2a352b89be6754052915a829105
|
|
| BLAKE2b-256 |
e0bd67173d33fe4b8e04ab6d42ba7afc4b162dfaaf229d4a43aa2888c1d6cff9
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
81583302570894ccbf05313070bd91e5ac9bace39566b87dae6c28c63d905845 - Sigstore transparency entry: 2684889585
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type:
File details
Details for the file solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 371.2 kB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ab8a85e118c7836136e67d912f78b52169e41fb1b49e6292737347516e63ff7
|
|
| MD5 |
d3c3f090011af7764fe1c565ebe92f38
|
|
| BLAKE2b-256 |
bb84b3ab9533bd6db8bcd9af99a043c80a050e2682765872de42b82f26586ed9
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
9ab8a85e118c7836136e67d912f78b52169e41fb1b49e6292737347516e63ff7 - Sigstore transparency entry: 2684889355
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type:
File details
Details for the file solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 372.2 kB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c249cf7430865ffc5ca53fc96120f281f5fd01d66d82084f91d265ca055fbb7
|
|
| MD5 |
2b1ce2decafa4a3054b0e9fe065594bc
|
|
| BLAKE2b-256 |
c858164d3a91ec86ef1feed0ec5cf1f829b2dc161d92990f1d5b786fea4bef99
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solidus_network-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl -
Subject digest:
0c249cf7430865ffc5ca53fc96120f281f5fd01d66d82084f91d265ca055fbb7 - Sigstore transparency entry: 2684889079
- Sigstore integration time:
-
Permalink:
solidusnetwork/solidus-py@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solidusnetwork
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@beaa0d061dc682cdad0a3f62b36c2d6db7d8ffda -
Trigger Event:
push
-
Statement type: