openbadgeslib 3.2.0__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/Changelog.txt +30 -0
- {openbadgeslib-3.2.0/openbadgeslib.egg-info → openbadgeslib-3.3.0}/PKG-INFO +82 -26
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/README.md +80 -24
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/config.ini.example +4 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/confparser.py +19 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/badge.py +19 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/__init__.py +8 -3
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/did.py +56 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/ldp.py +198 -12
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_signer.py +92 -12
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/util.py +1 -1
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0/openbadgeslib.egg-info}/PKG-INFO +82 -26
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/SOURCES.txt +2 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/pyproject.toml +2 -1
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/ldp_helpers.py +5 -3
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_confparser.py +25 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_did.py +52 -0
- openbadgeslib-3.3.0/tests/test_ob3_ldp_sign.py +292 -0
- openbadgeslib-3.3.0/tests/test_ob3_signer_cli_ldp.py +231 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/LICENSE.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/MANIFEST.in +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/docs/README.md +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/__init__.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/__init__.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/exceptions.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/utils.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/badge.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/baking.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/errors.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/keys.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/logs.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/mail.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/__init__.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/__init__.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/models.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/credential.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status_list.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status_registry.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_init.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_keygenerator.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_publish.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/py.typed +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/requires.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/top_level.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/setup.cfg +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/config1.ini +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/conftest.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/sample1.png +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/sample1.svg +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/userimage01.svg +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/logo Python Espan/314/203a.svg" +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/logo Python Espa/303/261a.svg" +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/runtests.sh +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_cli_json.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_cli_smoke.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_docs.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_eddsa.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_jws.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_key_operation.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_badge_io.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_cli.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_models.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_contexts.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_credential.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_did_doc.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_ldp.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_ldp_cli.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_publish_cli.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_signer.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status_list.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status_registry.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_verifier.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_sign_ecc.pem +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_sign_rsa.pem +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_util.py +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_verify_ecc.pem +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_verify_rsa.pem +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/withoutxmlheader.svg +0 -0
- {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/withxmlheader.svg +0 -0
|
@@ -4,6 +4,36 @@ OpenBadgesLib - Changelog
|
|
|
4
4
|
Newest first. Dates are ISO 8601 (YYYY-MM-DD).
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
* v3.3.0 - 2026-07-04
|
|
8
|
+
|
|
9
|
+
- feat(ob3): issuance of OB 3.0 credentials secured with a W3C Data
|
|
10
|
+
Integrity proof (eddsa-rdfc-2022) — new `OB3LdpSigner` and low-level
|
|
11
|
+
`add_data_integrity_proof`, the signing counterparts of the verifiers
|
|
12
|
+
shipped in 3.2.0, sharing their canonicalization core and reproducing
|
|
13
|
+
the official W3C vc-di-eddsa test vector byte for byte. Ed25519 keys
|
|
14
|
+
only; requires the `[ldp]` extra, which now covers both directions.
|
|
15
|
+
|
|
16
|
+
- feat(cli): `openbadges-signer -P/--proof-format {vc-jwt,ldp}` (OB3
|
|
17
|
+
only) plus an optional per-badge `proof_format` config key (flag wins;
|
|
18
|
+
default vc-jwt). With `[issuer] did` configured the proof names the
|
|
19
|
+
`did:web:...#badge_N` verification method `openbadges-publish -V 3`
|
|
20
|
+
publishes (trusted via `--resolve-did`); otherwise it falls back to a
|
|
21
|
+
self-asserted did:key derived from the signing key and tells the
|
|
22
|
+
operator so. LDP-signed badges are tagged `PROOF ldp` in the signer
|
|
23
|
+
audit log. Status lists stay VC-JWT regardless of the badge's proof
|
|
24
|
+
format.
|
|
25
|
+
|
|
26
|
+
- feat(ob3): multikey/did:key encoders in `ob3.did` —
|
|
27
|
+
`multikey_from_pem` (publicKeyMultibase) and `did_key_from_pem`,
|
|
28
|
+
the exact inverses of the resolvers, for Ed25519 and NIST P-256.
|
|
29
|
+
|
|
30
|
+
- fix(cli): `Badge.create_from_conf` rejected `key_type = ED25519`
|
|
31
|
+
outright, so the CLI could never sign with the keys
|
|
32
|
+
`openbadges-keygenerator -t ED25519` produces; Ed25519 key material
|
|
33
|
+
now loads, and the legacy OB 1.0 path (`-V 1`) refuses non-RSA/ECC
|
|
34
|
+
keys with a clean message instead of failing mid-JWS.
|
|
35
|
+
|
|
36
|
+
|
|
7
37
|
* v3.2.0 - 2026-07-03
|
|
8
38
|
|
|
9
39
|
- feat(ob3): verification of OB 3.0 credentials secured with a W3C Data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Production-ready Python library & CLI for Open Badges 3.0 (W3C Verifiable Credentials): issue, verify, and revoke JWT-VC credentials with Bitstring Status Lists and did:web — plus strict OB 2.0 and legacy OB 1.0.
|
|
5
5
|
Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
|
|
6
6
|
License: LGPLv3
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://github.com/luisgf/openbadgeslib/wiki
|
|
|
9
9
|
Project-URL: API Reference, https://luisgf.github.io/openbadgeslib/
|
|
10
10
|
Project-URL: Source, https://github.com/luisgf/openbadgeslib
|
|
11
11
|
Project-URL: Changelog, https://github.com/luisgf/openbadgeslib/blob/master/Changelog.txt
|
|
12
|
-
Keywords: openbadges,open-badges,verifiable-credentials,vc-jwt,w3c-vc,digital-credentials,did,did-web,bitstring-status-list,revocation,ob3,credentials,ed25519,cryptography
|
|
12
|
+
Keywords: openbadges,open-badges,verifiable-credentials,vc-jwt,w3c-vc,digital-credentials,did,did-web,bitstring-status-list,revocation,ob3,credentials,ed25519,cryptography,data-integrity,eddsa
|
|
13
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: Education
|
|
@@ -54,7 +54,8 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|
**A production-ready Python library & CLI for the full Open Badges 3.0 issuer
|
|
56
56
|
lifecycle** — issue [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model-2.0/)
|
|
57
|
-
as JWT-VC, bake them into SVG/PNG, verify them,
|
|
57
|
+
as JWT-VC or W3C Data Integrity (LDP) proofs, bake them into SVG/PNG, verify them,
|
|
58
|
+
and **revoke or suspend** them
|
|
58
59
|
with W3C Bitstring Status Lists and `did:web`. It also ships strict
|
|
59
60
|
**OpenBadges 2.0** (JWS / hosted assertions) and a frozen **OpenBadges 1.0**
|
|
60
61
|
legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
@@ -63,14 +64,14 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
63
64
|
|
|
64
65
|
- Sign badge images (SVG and PNG) as strict OB 2.0 JWS / hosted assertions (with a frozen OB 1.0 legacy format)
|
|
65
66
|
- Issue and verify OpenBadges 3.0 JWT-VC credentials
|
|
66
|
-
- Bake OB 3.0 JWT
|
|
67
|
+
- Bake OB 3.0 credentials (JWT-VC or Data Integrity JSON-LD) into SVG and PNG badge images
|
|
67
68
|
- RSA 2048-bit (RS256), ECC NIST P-256 (ES256), and Ed25519 (EdDSA) key support
|
|
68
69
|
- SHA-256 hashed recipient identity with salt (OB 2.0)
|
|
69
70
|
- Expiration and revocation checking
|
|
70
71
|
- Issuer-side OB 3.0 revocation and suspension: W3C Bitstring Status List
|
|
71
72
|
publication and `--revoke` / `--suspend` / `--unsuspend` management
|
|
72
|
-
-
|
|
73
|
-
`[ldp]` extra) in addition to JWT-VC
|
|
73
|
+
- Issue and verify OB 3.0 W3C Data Integrity credentials (`eddsa-rdfc-2022`,
|
|
74
|
+
optional `[ldp]` extra) in addition to JWT-VC
|
|
74
75
|
- did:web issuer identity: `did.json` generation and DID resolution
|
|
75
76
|
- Five command-line tools included
|
|
76
77
|
|
|
@@ -79,8 +80,10 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
79
80
|
- **The complete OB 3.0 issuer lifecycle in Python** — not just issuing, but
|
|
80
81
|
publishing trust artefacts (`did:web`) and revoking/suspending credentials via
|
|
81
82
|
Bitstring Status Lists, driven from the CLI or the library.
|
|
82
|
-
- **Native VC-JWT
|
|
83
|
-
(EdDSA)
|
|
83
|
+
- **Native VC-JWT signing** with RSA (RS256), ECC P-256 (ES256) and Ed25519
|
|
84
|
+
(EdDSA) — *plus* native **W3C Data Integrity / LDP** signing
|
|
85
|
+
(`eddsa-rdfc-2022`, Ed25519, optional `[ldp]` extra). Both proof formats
|
|
86
|
+
issue *and* verify offline, no external service.
|
|
84
87
|
- **Lean and typed** — `mypy --strict`, CI on Python 3.10–3.13, a small
|
|
85
88
|
dependency set, dataclasses + explicit validation (no Pydantic).
|
|
86
89
|
- **Dual-licensed** LGPLv3 (library) / BSD-2-Clause (CLI tools).
|
|
@@ -88,23 +91,39 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
88
91
|
### How it compares
|
|
89
92
|
|
|
90
93
|
Best-effort comparison from each project's public documentation as of July 2026;
|
|
91
|
-
"not documented" is shown as `—`. Corrections welcome via issue/PR.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
"not documented" is shown as `—`. Corrections welcome via issue/PR. The Python
|
|
95
|
+
Open Badges landscape splits into three groups: 1EdTech's **verify-only** OB 2.0
|
|
96
|
+
reference validator, heavyweight **Django server platforms** (the Badgr lineage),
|
|
97
|
+
and a thin tail of **standalone libraries** — of which openbadgeslib is the only
|
|
98
|
+
actively-maintained, pip-installable, offline one that covers all three OB
|
|
99
|
+
versions and both OB 3.0 proof formats.
|
|
100
|
+
|
|
101
|
+
| Capability | **openbadgeslib** | [validator-core](https://github.com/1EdTech/openbadges-validator-core) | [Badgr / mint-o-badges](https://github.com/mint-o-badges/badgr-server) (a) | [pyopenbadges](https://github.com/CoopCodeCommun/pyopenbadges) | [didkit](https://github.com/spruceid/didkit-python) (b) |
|
|
102
|
+
| --- | --- | --- | --- | --- | --- |
|
|
103
|
+
| Open Badges versions | 1.0, 2.0, **3.0** | 0.5–2.0 | 1.1, 2.0, 3.0 | 3.0 only | generic VC |
|
|
104
|
+
| Issue / verify | ✅ both | verify only | issue; partial verify | both | both (VC + VP) |
|
|
105
|
+
| OB 3.0 proof: VC-JWT (JOSE) | ✅ RS/ES/EdDSA | — | — | roadmap | ✅ not OB-aware |
|
|
106
|
+
| OB 3.0 proof: Data Integrity / LDP | ✅ issue + verify (`eddsa-rdfc-2022`) | — | issue only (Ed25519) | home-grown, non-conformant | older suites; no `rdfc-2022` in wheel |
|
|
107
|
+
| Revocation / suspension | ✅ W3C Bitstring Status List | hosted check | `1EdTechRevocationList` | ad-hoc flag | — |
|
|
108
|
+
| `did:web` (generate + resolve) | ✅ | — | — | — | resolve only |
|
|
109
|
+
| Image baking (SVG + PNG) | ✅ | unbake only | ✅ | — | — |
|
|
110
|
+
| Form factor | library + 5 CLI tools | library + CLI | Django server | library | binding |
|
|
111
|
+
| Typing / CI | `mypy --strict`, CI 3.10–3.13 | — | — | Pydantic | — |
|
|
112
|
+
| License | LGPLv3 / BSD-2 | Apache-2.0 | AGPL-3.0 | MIT/LGPL | Apache-2.0 |
|
|
113
|
+
|
|
114
|
+
(a) The actively-maintained community fork (open-educational-badges): it
|
|
115
|
+
genuinely issues OB 3.0 Data Integrity credentials, but only as a server (not a
|
|
116
|
+
pip library) and only with Ed25519 (no VC-JWT). The classic Concentric Sky
|
|
117
|
+
`badgr-server` is OB 2.0 and its canonical repo is gone; SURF's
|
|
118
|
+
`edubadges-server` delegates OB 3.0 signing to external agents.
|
|
119
|
+
(b) Generic W3C VC/DID toolkit (Rust `ssi` bindings), not Open Badges-aware, and
|
|
120
|
+
archived (read-only since July 2025).
|
|
121
|
+
|
|
122
|
+
For authoritative OB 2.0 *validation* semantics, 1EdTech's Python
|
|
123
|
+
[`openbadges-validator-core`](https://github.com/1EdTech/openbadges-validator-core)
|
|
124
|
+
and Node [`openbadges-validator`](https://github.com/1EdTech/openbadges-validator)
|
|
125
|
+
remain the reference; openbadgeslib focuses on the full issuer lifecycle across
|
|
126
|
+
all three OB versions.
|
|
108
127
|
|
|
109
128
|
## Requirements
|
|
110
129
|
|
|
@@ -191,7 +210,15 @@ with open('/tmp/signed_badge.svg', 'wb') as f:
|
|
|
191
210
|
|
|
192
211
|
For the frozen OpenBadges 1.0 legacy API (`Badge` / `Signer` / `Verifier`), import from `openbadgeslib.ob1` instead.
|
|
193
212
|
|
|
194
|
-
## Using the library — OpenBadges 3.0
|
|
213
|
+
## Using the library — OpenBadges 3.0
|
|
214
|
+
|
|
215
|
+
OB 3.0 credentials can be secured with either of the two proof formats the
|
|
216
|
+
spec allows: a compact **VC-JWT** (`OB3Signer`, RS256/ES256/EdDSA) or an
|
|
217
|
+
embedded **Data Integrity** proof (`OB3LdpSigner`, cryptosuite
|
|
218
|
+
`eddsa-rdfc-2022`, Ed25519 only — needs the `[ldp]` extra). Both bake into
|
|
219
|
+
the same SVG/PNG carriers and the verifier auto-detects the format.
|
|
220
|
+
|
|
221
|
+
### VC-JWT (JOSE)
|
|
195
222
|
|
|
196
223
|
```python
|
|
197
224
|
from openbadgeslib.ob3 import (
|
|
@@ -227,6 +254,35 @@ restored = verifier.verify(token, expected_recipient='recipient@example.com')
|
|
|
227
254
|
print('Recipient:', restored.recipient_id)
|
|
228
255
|
```
|
|
229
256
|
|
|
257
|
+
### Data Integrity (LDP)
|
|
258
|
+
|
|
259
|
+
Same credential, an embedded JSON-LD proof instead of a JWT. Requires an
|
|
260
|
+
Ed25519 signing key and the `[ldp]` extra (`pip install openbadgeslib[ldp]`).
|
|
261
|
+
Reuse the `credential` built above:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from openbadgeslib.ob3 import OB3LdpSigner, OB3LdpVerifier
|
|
265
|
+
|
|
266
|
+
with open('sign_ed25519.pem', 'rb') as f:
|
|
267
|
+
priv_pem = f.read()
|
|
268
|
+
|
|
269
|
+
# Bake a credential carrying an eddsa-rdfc-2022 DataIntegrityProof into an SVG
|
|
270
|
+
signer = OB3LdpSigner(priv_pem)
|
|
271
|
+
with open('badge.svg', 'rb') as f:
|
|
272
|
+
baked_svg = signer.sign_into_svg(credential, f.read())
|
|
273
|
+
|
|
274
|
+
# Verify (the LDP credential travels as JSON in the baked image)
|
|
275
|
+
with open('verify_ed25519.pem', 'rb') as f:
|
|
276
|
+
verifier = OB3LdpVerifier(pubkey_pem=f.read())
|
|
277
|
+
document = OB3Verifier.extract_token_from_svg(baked_svg)
|
|
278
|
+
restored = verifier.verify(document, expected_recipient='recipient@example.com')
|
|
279
|
+
print('Recipient:', restored.recipient_id)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
From the CLI, select the format with `openbadges-signer -P ldp` (OB 3.0 only),
|
|
283
|
+
or set `proof_format = ldp` in the badge's INI section; the default stays
|
|
284
|
+
`vc-jwt`. Status lists remain VC-JWT regardless of the badge's proof format.
|
|
285
|
+
|
|
230
286
|
## Documentation
|
|
231
287
|
|
|
232
288
|
- **User & developer guide** — the project [**Wiki**](https://github.com/luisgf/openbadgeslib/wiki):
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
**A production-ready Python library & CLI for the full Open Badges 3.0 issuer
|
|
9
9
|
lifecycle** — issue [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model-2.0/)
|
|
10
|
-
as JWT-VC, bake them into SVG/PNG, verify them,
|
|
10
|
+
as JWT-VC or W3C Data Integrity (LDP) proofs, bake them into SVG/PNG, verify them,
|
|
11
|
+
and **revoke or suspend** them
|
|
11
12
|
with W3C Bitstring Status Lists and `did:web`. It also ships strict
|
|
12
13
|
**OpenBadges 2.0** (JWS / hosted assertions) and a frozen **OpenBadges 1.0**
|
|
13
14
|
legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
@@ -16,14 +17,14 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
16
17
|
|
|
17
18
|
- Sign badge images (SVG and PNG) as strict OB 2.0 JWS / hosted assertions (with a frozen OB 1.0 legacy format)
|
|
18
19
|
- Issue and verify OpenBadges 3.0 JWT-VC credentials
|
|
19
|
-
- Bake OB 3.0 JWT
|
|
20
|
+
- Bake OB 3.0 credentials (JWT-VC or Data Integrity JSON-LD) into SVG and PNG badge images
|
|
20
21
|
- RSA 2048-bit (RS256), ECC NIST P-256 (ES256), and Ed25519 (EdDSA) key support
|
|
21
22
|
- SHA-256 hashed recipient identity with salt (OB 2.0)
|
|
22
23
|
- Expiration and revocation checking
|
|
23
24
|
- Issuer-side OB 3.0 revocation and suspension: W3C Bitstring Status List
|
|
24
25
|
publication and `--revoke` / `--suspend` / `--unsuspend` management
|
|
25
|
-
-
|
|
26
|
-
`[ldp]` extra) in addition to JWT-VC
|
|
26
|
+
- Issue and verify OB 3.0 W3C Data Integrity credentials (`eddsa-rdfc-2022`,
|
|
27
|
+
optional `[ldp]` extra) in addition to JWT-VC
|
|
27
28
|
- did:web issuer identity: `did.json` generation and DID resolution
|
|
28
29
|
- Five command-line tools included
|
|
29
30
|
|
|
@@ -32,8 +33,10 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
32
33
|
- **The complete OB 3.0 issuer lifecycle in Python** — not just issuing, but
|
|
33
34
|
publishing trust artefacts (`did:web`) and revoking/suspending credentials via
|
|
34
35
|
Bitstring Status Lists, driven from the CLI or the library.
|
|
35
|
-
- **Native VC-JWT
|
|
36
|
-
(EdDSA)
|
|
36
|
+
- **Native VC-JWT signing** with RSA (RS256), ECC P-256 (ES256) and Ed25519
|
|
37
|
+
(EdDSA) — *plus* native **W3C Data Integrity / LDP** signing
|
|
38
|
+
(`eddsa-rdfc-2022`, Ed25519, optional `[ldp]` extra). Both proof formats
|
|
39
|
+
issue *and* verify offline, no external service.
|
|
37
40
|
- **Lean and typed** — `mypy --strict`, CI on Python 3.10–3.13, a small
|
|
38
41
|
dependency set, dataclasses + explicit validation (no Pydantic).
|
|
39
42
|
- **Dual-licensed** LGPLv3 (library) / BSD-2-Clause (CLI tools).
|
|
@@ -41,23 +44,39 @@ legacy format, selected with `-V {1,2,3}` (default `3`).
|
|
|
41
44
|
### How it compares
|
|
42
45
|
|
|
43
46
|
Best-effort comparison from each project's public documentation as of July 2026;
|
|
44
|
-
"not documented" is shown as `—`. Corrections welcome via issue/PR.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
"not documented" is shown as `—`. Corrections welcome via issue/PR. The Python
|
|
48
|
+
Open Badges landscape splits into three groups: 1EdTech's **verify-only** OB 2.0
|
|
49
|
+
reference validator, heavyweight **Django server platforms** (the Badgr lineage),
|
|
50
|
+
and a thin tail of **standalone libraries** — of which openbadgeslib is the only
|
|
51
|
+
actively-maintained, pip-installable, offline one that covers all three OB
|
|
52
|
+
versions and both OB 3.0 proof formats.
|
|
53
|
+
|
|
54
|
+
| Capability | **openbadgeslib** | [validator-core](https://github.com/1EdTech/openbadges-validator-core) | [Badgr / mint-o-badges](https://github.com/mint-o-badges/badgr-server) (a) | [pyopenbadges](https://github.com/CoopCodeCommun/pyopenbadges) | [didkit](https://github.com/spruceid/didkit-python) (b) |
|
|
55
|
+
| --- | --- | --- | --- | --- | --- |
|
|
56
|
+
| Open Badges versions | 1.0, 2.0, **3.0** | 0.5–2.0 | 1.1, 2.0, 3.0 | 3.0 only | generic VC |
|
|
57
|
+
| Issue / verify | ✅ both | verify only | issue; partial verify | both | both (VC + VP) |
|
|
58
|
+
| OB 3.0 proof: VC-JWT (JOSE) | ✅ RS/ES/EdDSA | — | — | roadmap | ✅ not OB-aware |
|
|
59
|
+
| OB 3.0 proof: Data Integrity / LDP | ✅ issue + verify (`eddsa-rdfc-2022`) | — | issue only (Ed25519) | home-grown, non-conformant | older suites; no `rdfc-2022` in wheel |
|
|
60
|
+
| Revocation / suspension | ✅ W3C Bitstring Status List | hosted check | `1EdTechRevocationList` | ad-hoc flag | — |
|
|
61
|
+
| `did:web` (generate + resolve) | ✅ | — | — | — | resolve only |
|
|
62
|
+
| Image baking (SVG + PNG) | ✅ | unbake only | ✅ | — | — |
|
|
63
|
+
| Form factor | library + 5 CLI tools | library + CLI | Django server | library | binding |
|
|
64
|
+
| Typing / CI | `mypy --strict`, CI 3.10–3.13 | — | — | Pydantic | — |
|
|
65
|
+
| License | LGPLv3 / BSD-2 | Apache-2.0 | AGPL-3.0 | MIT/LGPL | Apache-2.0 |
|
|
66
|
+
|
|
67
|
+
(a) The actively-maintained community fork (open-educational-badges): it
|
|
68
|
+
genuinely issues OB 3.0 Data Integrity credentials, but only as a server (not a
|
|
69
|
+
pip library) and only with Ed25519 (no VC-JWT). The classic Concentric Sky
|
|
70
|
+
`badgr-server` is OB 2.0 and its canonical repo is gone; SURF's
|
|
71
|
+
`edubadges-server` delegates OB 3.0 signing to external agents.
|
|
72
|
+
(b) Generic W3C VC/DID toolkit (Rust `ssi` bindings), not Open Badges-aware, and
|
|
73
|
+
archived (read-only since July 2025).
|
|
74
|
+
|
|
75
|
+
For authoritative OB 2.0 *validation* semantics, 1EdTech's Python
|
|
76
|
+
[`openbadges-validator-core`](https://github.com/1EdTech/openbadges-validator-core)
|
|
77
|
+
and Node [`openbadges-validator`](https://github.com/1EdTech/openbadges-validator)
|
|
78
|
+
remain the reference; openbadgeslib focuses on the full issuer lifecycle across
|
|
79
|
+
all three OB versions.
|
|
61
80
|
|
|
62
81
|
## Requirements
|
|
63
82
|
|
|
@@ -144,7 +163,15 @@ with open('/tmp/signed_badge.svg', 'wb') as f:
|
|
|
144
163
|
|
|
145
164
|
For the frozen OpenBadges 1.0 legacy API (`Badge` / `Signer` / `Verifier`), import from `openbadgeslib.ob1` instead.
|
|
146
165
|
|
|
147
|
-
## Using the library — OpenBadges 3.0
|
|
166
|
+
## Using the library — OpenBadges 3.0
|
|
167
|
+
|
|
168
|
+
OB 3.0 credentials can be secured with either of the two proof formats the
|
|
169
|
+
spec allows: a compact **VC-JWT** (`OB3Signer`, RS256/ES256/EdDSA) or an
|
|
170
|
+
embedded **Data Integrity** proof (`OB3LdpSigner`, cryptosuite
|
|
171
|
+
`eddsa-rdfc-2022`, Ed25519 only — needs the `[ldp]` extra). Both bake into
|
|
172
|
+
the same SVG/PNG carriers and the verifier auto-detects the format.
|
|
173
|
+
|
|
174
|
+
### VC-JWT (JOSE)
|
|
148
175
|
|
|
149
176
|
```python
|
|
150
177
|
from openbadgeslib.ob3 import (
|
|
@@ -180,6 +207,35 @@ restored = verifier.verify(token, expected_recipient='recipient@example.com')
|
|
|
180
207
|
print('Recipient:', restored.recipient_id)
|
|
181
208
|
```
|
|
182
209
|
|
|
210
|
+
### Data Integrity (LDP)
|
|
211
|
+
|
|
212
|
+
Same credential, an embedded JSON-LD proof instead of a JWT. Requires an
|
|
213
|
+
Ed25519 signing key and the `[ldp]` extra (`pip install openbadgeslib[ldp]`).
|
|
214
|
+
Reuse the `credential` built above:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from openbadgeslib.ob3 import OB3LdpSigner, OB3LdpVerifier
|
|
218
|
+
|
|
219
|
+
with open('sign_ed25519.pem', 'rb') as f:
|
|
220
|
+
priv_pem = f.read()
|
|
221
|
+
|
|
222
|
+
# Bake a credential carrying an eddsa-rdfc-2022 DataIntegrityProof into an SVG
|
|
223
|
+
signer = OB3LdpSigner(priv_pem)
|
|
224
|
+
with open('badge.svg', 'rb') as f:
|
|
225
|
+
baked_svg = signer.sign_into_svg(credential, f.read())
|
|
226
|
+
|
|
227
|
+
# Verify (the LDP credential travels as JSON in the baked image)
|
|
228
|
+
with open('verify_ed25519.pem', 'rb') as f:
|
|
229
|
+
verifier = OB3LdpVerifier(pubkey_pem=f.read())
|
|
230
|
+
document = OB3Verifier.extract_token_from_svg(baked_svg)
|
|
231
|
+
restored = verifier.verify(document, expected_recipient='recipient@example.com')
|
|
232
|
+
print('Recipient:', restored.recipient_id)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
From the CLI, select the format with `openbadges-signer -P ldp` (OB 3.0 only),
|
|
236
|
+
or set `proof_format = ldp` in the badge's INI section; the default stays
|
|
237
|
+
`vc-jwt`. Status lists remain VC-JWT regardless of the badge's proof format.
|
|
238
|
+
|
|
183
239
|
## Documentation
|
|
184
240
|
|
|
185
241
|
- **User & developer guide** — the project [**Wiki**](https://github.com/luisgf/openbadgeslib/wiki):
|
|
@@ -58,6 +58,10 @@ private_key = ${paths:base_key}/sign_rsa_key_1.pem
|
|
|
58
58
|
public_key = ${paths:base_key}/verify_rsa_key_1.pem
|
|
59
59
|
; key_type selects the algorithm for openbadges-keygenerator: RSA (default), ECC, or ED25519
|
|
60
60
|
key_type = RSA
|
|
61
|
+
; OpenBadges 3.0 proof format: vc-jwt (compact JWT-VC, the default) or ldp
|
|
62
|
+
; (embedded W3C Data Integrity proof, cryptosuite eddsa-rdfc-2022). ldp needs
|
|
63
|
+
; key_type = ED25519 and the [ldp] extra (pip install openbadgeslib[ldp]).
|
|
64
|
+
;proof_format = vc-jwt
|
|
61
65
|
; OpenBadges 3.0 credential status (opt-in). When set, openbadges-signer -V 3
|
|
62
66
|
; attaches credentialStatus entries for these purposes (comma-separated subset
|
|
63
67
|
; of: revocation, suspension) and openbadges-publish -V 3 generates the signed
|
|
@@ -79,6 +79,25 @@ def ob3_issuer_id(conf: ConfigParser) -> str:
|
|
|
79
79
|
"[issuer] did must be 'auto' or a did:... identifier, got %r" % did)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
#: Proof formats an OB3 credential can be issued with — the same vocabulary
|
|
83
|
+
#: openbadges-verifier reports in its JSON output.
|
|
84
|
+
OB3_PROOF_FORMATS = ('vc-jwt', 'ldp')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def ob3_proof_format(conf: ConfigParser, badge_section: str) -> str:
|
|
88
|
+
"""Return the proof format OB3 credentials of *badge_section* use:
|
|
89
|
+
``vc-jwt`` (compact JWT-VC, the default) or ``ldp`` (embedded Data
|
|
90
|
+
Integrity proof, cryptosuite eddsa-rdfc-2022 — needs an Ed25519 key and
|
|
91
|
+
the [ldp] extra). Raises ValueError for anything else.
|
|
92
|
+
"""
|
|
93
|
+
value = (conf[badge_section].get('proof_format') or 'vc-jwt').strip()
|
|
94
|
+
if value in OB3_PROOF_FORMATS:
|
|
95
|
+
return value
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"[%s] proof_format must be one of %s, got %r"
|
|
98
|
+
% (badge_section, ', '.join(OB3_PROOF_FORMATS), value))
|
|
99
|
+
|
|
100
|
+
|
|
82
101
|
@dataclass
|
|
83
102
|
class OB3StatusConfig:
|
|
84
103
|
"""Resolved per-badge OB3 credential status configuration."""
|
|
@@ -145,6 +145,25 @@ class Badge():
|
|
|
145
145
|
self.priv_key = SigningKey.from_pem(self.privkey_pem)
|
|
146
146
|
except Exception as exc:
|
|
147
147
|
raise PrivateKeyReadError('Unable to read ECC private key: %s' % exc) from exc
|
|
148
|
+
elif self.key_type is KeyType.ED25519:
|
|
149
|
+
from cryptography.hazmat.primitives.serialization import (
|
|
150
|
+
load_pem_private_key, load_pem_public_key)
|
|
151
|
+
if self.pubkey_pem:
|
|
152
|
+
try:
|
|
153
|
+
pub_pem = self.pubkey_pem.encode('utf-8') \
|
|
154
|
+
if isinstance(self.pubkey_pem, str) else self.pubkey_pem
|
|
155
|
+
self.pub_key = load_pem_public_key(pub_pem)
|
|
156
|
+
except Exception as exc:
|
|
157
|
+
raise PublicKeyReadError(
|
|
158
|
+
'Unable to read Ed25519 public key: %s' % exc) from exc
|
|
159
|
+
if self.privkey_pem:
|
|
160
|
+
try:
|
|
161
|
+
priv_pem = self.privkey_pem.encode('utf-8') \
|
|
162
|
+
if isinstance(self.privkey_pem, str) else self.privkey_pem
|
|
163
|
+
self.priv_key = load_pem_private_key(priv_pem, password=None)
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
raise PrivateKeyReadError(
|
|
166
|
+
'Unable to read Ed25519 private key: %s' % exc) from exc
|
|
148
167
|
elif self.key_type is not None:
|
|
149
168
|
# key_type=None is a valid "no key material yet" state; any other
|
|
150
169
|
# value is an unsupported key type and must fail loudly.
|
|
@@ -23,29 +23,34 @@
|
|
|
23
23
|
from .credential import Achievement, Issuer, OpenBadgeCredential
|
|
24
24
|
from .signer import OB3Signer
|
|
25
25
|
from .verifier import OB3VerificationError, OB3Verifier
|
|
26
|
-
from .ldp import OB3LdpVerifier,
|
|
26
|
+
from .ldp import (OB3LdpSigner, OB3LdpVerifier, add_data_integrity_proof,
|
|
27
|
+
verify_data_integrity_proof)
|
|
27
28
|
from .status import check_credential_status
|
|
28
29
|
from .status_list import (build_status_list_credential, encode_bitstring,
|
|
29
30
|
sign_status_list_credential, status_entry)
|
|
30
31
|
from .status_registry import StatusRegistry
|
|
31
|
-
from .did import (build_did_document,
|
|
32
|
-
resolve_verification_method)
|
|
32
|
+
from .did import (build_did_document, did_key_from_pem, did_web_from_url,
|
|
33
|
+
multikey_from_pem, resolve_did, resolve_verification_method)
|
|
33
34
|
|
|
34
35
|
__all__ = [
|
|
35
36
|
'Achievement',
|
|
36
37
|
'Issuer',
|
|
37
38
|
'OpenBadgeCredential',
|
|
39
|
+
'OB3LdpSigner',
|
|
38
40
|
'OB3LdpVerifier',
|
|
39
41
|
'OB3Signer',
|
|
40
42
|
'OB3Verifier',
|
|
41
43
|
'OB3VerificationError',
|
|
44
|
+
'add_data_integrity_proof',
|
|
42
45
|
'verify_data_integrity_proof',
|
|
43
46
|
'StatusRegistry',
|
|
44
47
|
'build_did_document',
|
|
45
48
|
'build_status_list_credential',
|
|
46
49
|
'check_credential_status',
|
|
50
|
+
'did_key_from_pem',
|
|
47
51
|
'did_web_from_url',
|
|
48
52
|
'encode_bitstring',
|
|
53
|
+
'multikey_from_pem',
|
|
49
54
|
'resolve_did',
|
|
50
55
|
'resolve_verification_method',
|
|
51
56
|
'sign_status_list_credential',
|
|
@@ -117,6 +117,62 @@ def _read_varint(data: bytes) -> Any:
|
|
|
117
117
|
raise OB3VerificationError("truncated multicodec varint")
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
def _b58btc_encode(data: bytes) -> str:
|
|
121
|
+
num = int.from_bytes(data, 'big')
|
|
122
|
+
out = ''
|
|
123
|
+
while num > 0:
|
|
124
|
+
num, rem = divmod(num, 58)
|
|
125
|
+
out = _B58_ALPHABET[rem] + out
|
|
126
|
+
n_pad = len(data) - len(data.lstrip(b'\x00')) # leading 0x00 => leading '1'
|
|
127
|
+
return '1' * n_pad + out
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _write_varint(code: int) -> bytes:
|
|
131
|
+
if code < 0:
|
|
132
|
+
raise ValueError('varint codes are unsigned, got %d' % code)
|
|
133
|
+
out = bytearray()
|
|
134
|
+
while True:
|
|
135
|
+
byte = code & 0x7f
|
|
136
|
+
code >>= 7
|
|
137
|
+
out.append(byte | 0x80 if code else byte)
|
|
138
|
+
if not code:
|
|
139
|
+
return bytes(out)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def multikey_from_pem(pubkey_pem: Any) -> str:
|
|
143
|
+
"""Encode a public key PEM as a Multikey ``publicKeyMultibase`` string:
|
|
144
|
+
'z' + base58btc(multicodec varint + raw key bytes) — the exact inverse of
|
|
145
|
+
the did:key/publicKeyMultibase decoding above. Supports Ed25519 (0xed)
|
|
146
|
+
and NIST P-256 (0x1200, compressed point); raises ValueError otherwise
|
|
147
|
+
(this runs issuer-side, on the operator's own key material).
|
|
148
|
+
"""
|
|
149
|
+
if isinstance(pubkey_pem, str):
|
|
150
|
+
pubkey_pem = pubkey_pem.encode('utf-8')
|
|
151
|
+
pub = _serialization.load_pem_public_key(pubkey_pem)
|
|
152
|
+
if isinstance(pub, Ed25519PublicKey):
|
|
153
|
+
prefixed = _write_varint(_MULTICODEC_ED25519_PUB) + pub.public_bytes(
|
|
154
|
+
_serialization.Encoding.Raw, _serialization.PublicFormat.Raw)
|
|
155
|
+
elif isinstance(pub, ec.EllipticCurvePublicKey) \
|
|
156
|
+
and isinstance(pub.curve, ec.SECP256R1):
|
|
157
|
+
prefixed = _write_varint(_MULTICODEC_P256_PUB) + pub.public_bytes(
|
|
158
|
+
_serialization.Encoding.X962,
|
|
159
|
+
_serialization.PublicFormat.CompressedPoint)
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError('multikey encoding supports Ed25519 and NIST P-256 '
|
|
162
|
+
'public keys, got %s' % type(pub).__name__)
|
|
163
|
+
return 'z' + _b58btc_encode(prefixed)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def did_key_from_pem(pubkey_pem: Any) -> str:
|
|
167
|
+
"""Derive the did:key identifier of a public key PEM (Ed25519 or P-256).
|
|
168
|
+
|
|
169
|
+
Self-certifying: the key IS the identifier, so resolving the returned DID
|
|
170
|
+
with :func:`resolve_did` yields the same key back. Raises ValueError for
|
|
171
|
+
unsupported key types.
|
|
172
|
+
"""
|
|
173
|
+
return 'did:key:' + multikey_from_pem(pubkey_pem)
|
|
174
|
+
|
|
175
|
+
|
|
120
176
|
# ── did:web ──────────────────────────────────────────────────────────────────
|
|
121
177
|
|
|
122
178
|
def _resolve_did_web(did: str, fetch: Any) -> bytes:
|