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.
Files changed (109) hide show
  1. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/Changelog.txt +30 -0
  2. {openbadgeslib-3.2.0/openbadgeslib.egg-info → openbadgeslib-3.3.0}/PKG-INFO +82 -26
  3. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/README.md +80 -24
  4. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/config.ini.example +4 -0
  5. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/confparser.py +19 -0
  6. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/badge.py +19 -0
  7. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/__init__.py +8 -3
  8. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/did.py +56 -0
  9. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/ldp.py +198 -12
  10. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_signer.py +92 -12
  11. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/util.py +1 -1
  12. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0/openbadgeslib.egg-info}/PKG-INFO +82 -26
  13. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/SOURCES.txt +2 -0
  14. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/pyproject.toml +2 -1
  15. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/ldp_helpers.py +5 -3
  16. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_confparser.py +25 -0
  17. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_did.py +52 -0
  18. openbadgeslib-3.3.0/tests/test_ob3_ldp_sign.py +292 -0
  19. openbadgeslib-3.3.0/tests/test_ob3_signer_cli_ldp.py +231 -0
  20. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/LICENSE.txt +0 -0
  21. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/MANIFEST.in +0 -0
  22. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/docs/README.md +0 -0
  23. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/__init__.py +0 -0
  24. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/__init__.py +0 -0
  25. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/exceptions.py +0 -0
  26. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/_jws/utils.py +0 -0
  27. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/badge.py +0 -0
  28. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/baking.py +0 -0
  29. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/errors.py +0 -0
  30. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/keys.py +0 -0
  31. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/logs.py +0 -0
  32. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/mail.py +0 -0
  33. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/__init__.py +0 -0
  34. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/signer.py +0 -0
  35. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob1/verifier.py +0 -0
  36. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/__init__.py +0 -0
  37. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/models.py +0 -0
  38. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/signer.py +0 -0
  39. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob2/verifier.py +0 -0
  40. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
  41. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
  42. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
  43. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
  44. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
  45. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
  46. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
  47. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/credential.py +0 -0
  48. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/signer.py +0 -0
  49. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status.py +0 -0
  50. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status_list.py +0 -0
  51. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/status_registry.py +0 -0
  52. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/ob3/verifier.py +0 -0
  53. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_init.py +0 -0
  54. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_keygenerator.py +0 -0
  55. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_publish.py +0 -0
  56. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/openbadges_verifier.py +0 -0
  57. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/py.typed +0 -0
  58. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/signer.py +0 -0
  59. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib/verifier.py +0 -0
  60. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
  61. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
  62. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/requires.txt +0 -0
  63. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/openbadgeslib.egg-info/top_level.txt +0 -0
  64. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/setup.cfg +0 -0
  65. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/config1.ini +0 -0
  66. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/conftest.py +0 -0
  67. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
  68. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
  69. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
  70. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
  71. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
  72. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
  73. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/sample1.png +0 -0
  74. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/sample1.svg +0 -0
  75. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/images/userimage01.svg +0 -0
  76. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/logo Python Espan/314/203a.svg" +0 -0
  77. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/logo Python Espa/303/261a.svg" +0 -0
  78. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/runtests.sh +0 -0
  79. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_cli_json.py +0 -0
  80. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_cli_smoke.py +0 -0
  81. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_docs.py +0 -0
  82. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_eddsa.py +0 -0
  83. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_jws.py +0 -0
  84. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_key_operation.py +0 -0
  85. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_badge_io.py +0 -0
  86. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_signer.py +0 -0
  87. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob1_verifier.py +0 -0
  88. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_cli.py +0 -0
  89. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_models.py +0 -0
  90. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_signer.py +0 -0
  91. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob2_verifier.py +0 -0
  92. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_contexts.py +0 -0
  93. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_credential.py +0 -0
  94. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_did_doc.py +0 -0
  95. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_ldp.py +0 -0
  96. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_ldp_cli.py +0 -0
  97. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_publish_cli.py +0 -0
  98. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_signer.py +0 -0
  99. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status.py +0 -0
  100. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status_list.py +0 -0
  101. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_status_registry.py +0 -0
  102. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_ob3_verifier.py +0 -0
  103. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_sign_ecc.pem +0 -0
  104. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_sign_rsa.pem +0 -0
  105. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_util.py +0 -0
  106. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_verify_ecc.pem +0 -0
  107. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/test_verify_rsa.pem +0 -0
  108. {openbadgeslib-3.2.0 → openbadgeslib-3.3.0}/tests/withoutxmlheader.svg +0 -0
  109. {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.2.0
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, and **revoke or suspend** 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 tokens into SVG and PNG badge images
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
- - Verify OB 3.0 W3C Data Integrity credentials (`eddsa-rdfc-2022`, optional
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** signing with RSA (RS256), ECC P-256 (ES256) and Ed25519
83
- (EdDSA), and offline verification.
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
- | Capability | **openbadgeslib** | [PyOpenBadges](https://github.com/CoopCodeCommun/pyopenbadges) |
94
- | --- | --- | --- |
95
- | Open Badges versions | 1.0, 2.0, **3.0** | 3.0 (v2→v3 planned) |
96
- | OB 3.0 proof: VC-JWT (JOSE) | ✅ RS/ES/EdDSA | roadmap |
97
- | OB 3.0 proof: Data Integrity / LDP | ✅ verify (`eddsa-rdfc-2022`, `[ldp]` extra) | JSON-LD signing (format unspecified) |
98
- | Revocation / suspension (Bitstring Status List) | ✅ issue + publish + manage | — |
99
- | `did:web` (generate + resolve) | ✅ | — |
100
- | Image baking (SVG + PNG) | ✅ | roadmap |
101
- | Command-line tools | ✅ 5 tools | — |
102
- | Typing / CI | `mypy --strict`, CI 3.10–3.13 | — |
103
- | Validation | dataclasses + explicit checks | Pydantic |
104
-
105
- For OB 2.0 validation the reference implementation is 1EdTech's Node
106
- [`openbadges-validator`](https://github.com/1EdTech/openbadges-validator); this
107
- table focuses on the OB 3.0 Python landscape.
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 (JWT-VC)
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, and **revoke or suspend** 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 tokens into SVG and PNG badge images
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
- - Verify OB 3.0 W3C Data Integrity credentials (`eddsa-rdfc-2022`, optional
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** signing with RSA (RS256), ECC P-256 (ES256) and Ed25519
36
- (EdDSA), and offline verification.
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
- | Capability | **openbadgeslib** | [PyOpenBadges](https://github.com/CoopCodeCommun/pyopenbadges) |
47
- | --- | --- | --- |
48
- | Open Badges versions | 1.0, 2.0, **3.0** | 3.0 (v2→v3 planned) |
49
- | OB 3.0 proof: VC-JWT (JOSE) | ✅ RS/ES/EdDSA | roadmap |
50
- | OB 3.0 proof: Data Integrity / LDP | ✅ verify (`eddsa-rdfc-2022`, `[ldp]` extra) | JSON-LD signing (format unspecified) |
51
- | Revocation / suspension (Bitstring Status List) | ✅ issue + publish + manage | — |
52
- | `did:web` (generate + resolve) | ✅ | — |
53
- | Image baking (SVG + PNG) | ✅ | roadmap |
54
- | Command-line tools | ✅ 5 tools | — |
55
- | Typing / CI | `mypy --strict`, CI 3.10–3.13 | — |
56
- | Validation | dataclasses + explicit checks | Pydantic |
57
-
58
- For OB 2.0 validation the reference implementation is 1EdTech's Node
59
- [`openbadges-validator`](https://github.com/1EdTech/openbadges-validator); this
60
- table focuses on the OB 3.0 Python landscape.
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 (JWT-VC)
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, verify_data_integrity_proof
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, did_web_from_url, resolve_did,
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: