signedby 0.1.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.
@@ -0,0 +1,329 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "base64"
13
+ version = "0.21.7"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
16
+
17
+ [[package]]
18
+ name = "bech32"
19
+ version = "0.11.1"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
22
+
23
+ [[package]]
24
+ name = "bitflags"
25
+ version = "2.11.1"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
28
+
29
+ [[package]]
30
+ name = "cfg-if"
31
+ version = "1.0.4"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
34
+
35
+ [[package]]
36
+ name = "heck"
37
+ version = "0.4.1"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
40
+
41
+ [[package]]
42
+ name = "hex"
43
+ version = "0.4.3"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
46
+
47
+ [[package]]
48
+ name = "indoc"
49
+ version = "2.0.7"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
52
+ dependencies = [
53
+ "rustversion",
54
+ ]
55
+
56
+ [[package]]
57
+ name = "itoa"
58
+ version = "1.0.18"
59
+ source = "registry+https://github.com/rust-lang/crates.io-index"
60
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
61
+
62
+ [[package]]
63
+ name = "libc"
64
+ version = "0.2.185"
65
+ source = "registry+https://github.com/rust-lang/crates.io-index"
66
+ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
67
+
68
+ [[package]]
69
+ name = "lock_api"
70
+ version = "0.4.14"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
73
+ dependencies = [
74
+ "scopeguard",
75
+ ]
76
+
77
+ [[package]]
78
+ name = "memchr"
79
+ version = "2.8.0"
80
+ source = "registry+https://github.com/rust-lang/crates.io-index"
81
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
82
+
83
+ [[package]]
84
+ name = "memoffset"
85
+ version = "0.9.1"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
88
+ dependencies = [
89
+ "autocfg",
90
+ ]
91
+
92
+ [[package]]
93
+ name = "once_cell"
94
+ version = "1.21.4"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
97
+
98
+ [[package]]
99
+ name = "parking_lot"
100
+ version = "0.12.5"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
103
+ dependencies = [
104
+ "lock_api",
105
+ "parking_lot_core",
106
+ ]
107
+
108
+ [[package]]
109
+ name = "parking_lot_core"
110
+ version = "0.9.12"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
113
+ dependencies = [
114
+ "cfg-if",
115
+ "libc",
116
+ "redox_syscall",
117
+ "smallvec",
118
+ "windows-link",
119
+ ]
120
+
121
+ [[package]]
122
+ name = "portable-atomic"
123
+ version = "1.13.1"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
126
+
127
+ [[package]]
128
+ name = "proc-macro2"
129
+ version = "1.0.106"
130
+ source = "registry+https://github.com/rust-lang/crates.io-index"
131
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
132
+ dependencies = [
133
+ "unicode-ident",
134
+ ]
135
+
136
+ [[package]]
137
+ name = "pyo3"
138
+ version = "0.20.3"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233"
141
+ dependencies = [
142
+ "cfg-if",
143
+ "indoc",
144
+ "libc",
145
+ "memoffset",
146
+ "parking_lot",
147
+ "portable-atomic",
148
+ "pyo3-build-config",
149
+ "pyo3-ffi",
150
+ "pyo3-macros",
151
+ "unindent",
152
+ ]
153
+
154
+ [[package]]
155
+ name = "pyo3-build-config"
156
+ version = "0.20.3"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7"
159
+ dependencies = [
160
+ "once_cell",
161
+ "target-lexicon",
162
+ ]
163
+
164
+ [[package]]
165
+ name = "pyo3-ffi"
166
+ version = "0.20.3"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa"
169
+ dependencies = [
170
+ "libc",
171
+ "pyo3-build-config",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "pyo3-macros"
176
+ version = "0.20.3"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158"
179
+ dependencies = [
180
+ "proc-macro2",
181
+ "pyo3-macros-backend",
182
+ "quote",
183
+ "syn",
184
+ ]
185
+
186
+ [[package]]
187
+ name = "pyo3-macros-backend"
188
+ version = "0.20.3"
189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
190
+ checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185"
191
+ dependencies = [
192
+ "heck",
193
+ "proc-macro2",
194
+ "pyo3-build-config",
195
+ "quote",
196
+ "syn",
197
+ ]
198
+
199
+ [[package]]
200
+ name = "quote"
201
+ version = "1.0.45"
202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
203
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
204
+ dependencies = [
205
+ "proc-macro2",
206
+ ]
207
+
208
+ [[package]]
209
+ name = "redox_syscall"
210
+ version = "0.5.18"
211
+ source = "registry+https://github.com/rust-lang/crates.io-index"
212
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
213
+ dependencies = [
214
+ "bitflags",
215
+ ]
216
+
217
+ [[package]]
218
+ name = "rustversion"
219
+ version = "1.0.22"
220
+ source = "registry+https://github.com/rust-lang/crates.io-index"
221
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
222
+
223
+ [[package]]
224
+ name = "scopeguard"
225
+ version = "1.2.0"
226
+ source = "registry+https://github.com/rust-lang/crates.io-index"
227
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
228
+
229
+ [[package]]
230
+ name = "serde"
231
+ version = "1.0.228"
232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
233
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
234
+ dependencies = [
235
+ "serde_core",
236
+ "serde_derive",
237
+ ]
238
+
239
+ [[package]]
240
+ name = "serde_core"
241
+ version = "1.0.228"
242
+ source = "registry+https://github.com/rust-lang/crates.io-index"
243
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
244
+ dependencies = [
245
+ "serde_derive",
246
+ ]
247
+
248
+ [[package]]
249
+ name = "serde_derive"
250
+ version = "1.0.228"
251
+ source = "registry+https://github.com/rust-lang/crates.io-index"
252
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
253
+ dependencies = [
254
+ "proc-macro2",
255
+ "quote",
256
+ "syn",
257
+ ]
258
+
259
+ [[package]]
260
+ name = "serde_json"
261
+ version = "1.0.149"
262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
263
+ checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
264
+ dependencies = [
265
+ "itoa",
266
+ "memchr",
267
+ "serde",
268
+ "serde_core",
269
+ "zmij",
270
+ ]
271
+
272
+ [[package]]
273
+ name = "signedby-python"
274
+ version = "0.1.0"
275
+ dependencies = [
276
+ "base64",
277
+ "bech32",
278
+ "hex",
279
+ "pyo3",
280
+ "serde",
281
+ "serde_json",
282
+ ]
283
+
284
+ [[package]]
285
+ name = "smallvec"
286
+ version = "1.15.1"
287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
288
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
289
+
290
+ [[package]]
291
+ name = "syn"
292
+ version = "2.0.117"
293
+ source = "registry+https://github.com/rust-lang/crates.io-index"
294
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
295
+ dependencies = [
296
+ "proc-macro2",
297
+ "quote",
298
+ "unicode-ident",
299
+ ]
300
+
301
+ [[package]]
302
+ name = "target-lexicon"
303
+ version = "0.12.16"
304
+ source = "registry+https://github.com/rust-lang/crates.io-index"
305
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
306
+
307
+ [[package]]
308
+ name = "unicode-ident"
309
+ version = "1.0.24"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
312
+
313
+ [[package]]
314
+ name = "unindent"
315
+ version = "0.2.4"
316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
317
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
318
+
319
+ [[package]]
320
+ name = "windows-link"
321
+ version = "0.2.1"
322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
323
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
324
+
325
+ [[package]]
326
+ name = "zmij"
327
+ version = "1.0.21"
328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
329
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "signedby-python"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Python bindings for SignedByMe SDK"
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [lib]
10
+ name = "signedby"
11
+ crate-type = ["cdylib"]
12
+
13
+ [dependencies]
14
+ # PyO3 for Python bindings
15
+ pyo3 = { version = "0.20", features = ["extension-module"] }
16
+
17
+ # Serialization
18
+ serde = { version = "1.0", features = ["derive"] }
19
+ serde_json = "1.0"
20
+
21
+ # Encoding
22
+ hex = "0.4"
23
+ bech32 = "0.11"
24
+ base64 = "0.21"
signedby-0.1.0/LICENSE ADDED
@@ -0,0 +1,164 @@
1
+ SIGNEDBYME SOURCE-AVAILABLE LICENSE v1.0
2
+ (“SSAL-1.0”)
3
+
4
+ Copyright © 2025–2026 Privacy Lion LLC (d/b/a SignedByMe).
5
+ All rights reserved.
6
+
7
+ This license is “source-available.” It is NOT an OSI Open Source license prior to
8
+ the Change Date. On the Change Date, the Licensed Work automatically becomes
9
+ available under the Apache License, Version 2.0, and the restrictions in this
10
+ license terminate.
11
+
12
+ ----------------------------------------------------------------------
13
+ 1. DEFINITIONS
14
+ ----------------------------------------------------------------------
15
+
16
+ “Licensor” means Privacy Lion LLC (d/b/a SignedByMe).
17
+
18
+ “Licensed Work” means all source code, build scripts, configuration, and other
19
+ materials included in this repository, including iOS, Android, and API/server code.
20
+
21
+ “Derivative Work” means any work based on or derived from the Licensed Work.
22
+
23
+ “Distribute” means to provide, publish, sublicense, sell, license, or otherwise
24
+ make the Licensed Work (or any Derivative Work) available to any third party,
25
+ including by publishing to an application store or distributing binaries.
26
+
27
+ “Make Available” means to operate the Licensed Work (or any Derivative Work) as a
28
+ network-accessible service for the benefit of third parties, including offering a
29
+ hosted API or hosted service.
30
+
31
+ “Competing Application” means any application, service, or product (including any
32
+ hosted API) offered to third parties that provides substantially similar
33
+ functionality to SignedByMe as a core or significant feature, including any
34
+ combination of:
35
+
36
+ (a) key-based signature authorization flows (e.g., DID/private-key signing
37
+ performed locally by a user);
38
+
39
+ (b) paid-login, payment-gated, or otherwise monetized authorization; and/or
40
+
41
+ (c) issuance of authentication artifacts (including OIDC/OAuth-compatible tokens
42
+ or functional equivalents) based on such authorization/proofs.
43
+
44
+ ----------------------------------------------------------------------
45
+ 2. LICENSE GRANT (NON-COMPETING USE)
46
+ ----------------------------------------------------------------------
47
+
48
+ Subject to the restriction in Section 3, the Licensor grants you a worldwide,
49
+ non-exclusive, royalty-free license to copy, modify, and create Derivative Works
50
+ of the Licensed Work, and to Distribute the Licensed Work or Derivative Works,
51
+ solely for Non-Competing Use.
52
+
53
+ “Non-Competing Use” means any use that is NOT:
54
+ (i) Distributing the Licensed Work (or any Derivative Work) as part of a
55
+ Competing Application; and NOT
56
+ (ii) Making Available the Licensed Work (or any Derivative Work) as part of a
57
+ Competing Application.
58
+
59
+ Examples of permitted Non-Competing Use include: evaluation, testing, security
60
+ research, personal projects, internal company tools, contributions back to this
61
+ repository, and integration into unrelated products/services that are not a
62
+ Competing Application.
63
+
64
+ ----------------------------------------------------------------------
65
+ 3. RESTRICTION (NO COMPETING APPLICATIONS/SERVICES)
66
+ ----------------------------------------------------------------------
67
+
68
+ You may NOT Distribute or Make Available the Licensed Work (or any Derivative Work)
69
+ as part of a Competing Application.
70
+
71
+ This restriction applies regardless of whether the Competing Application is free
72
+ or paid, open-source or proprietary, self-hosted or hosted for others.
73
+
74
+ ----------------------------------------------------------------------
75
+ 4. TERMINATION
76
+ ----------------------------------------------------------------------
77
+
78
+ Any violation of Section 3 automatically terminates the license granted to you
79
+ under this Agreement. Upon termination, you must immediately cease use, Distribute
80
+ no further copies, and stop Making Available any service based on the Licensed Work.
81
+
82
+ ----------------------------------------------------------------------
83
+ 5. CHANGE DATE AND CHANGE LICENSE
84
+ ----------------------------------------------------------------------
85
+
86
+ Change Date: 2030-02-18
87
+
88
+ On the Change Date, the restrictions in this license automatically terminate and
89
+ the Licensed Work becomes fully available under the Apache License, Version 2.0
90
+ (no further action required from Licensor or the user).
91
+
92
+ Change License: Apache License, Version 2.0
93
+
94
+ A copy of the Apache 2.0 license text is provided in this repository as
95
+ LICENSE-APACHE.
96
+
97
+ ----------------------------------------------------------------------
98
+ 6. CONTRIBUTIONS
99
+ ----------------------------------------------------------------------
100
+
101
+ If you submit a contribution (including code, documentation, or other materials)
102
+ to the Licensed Work (e.g., via pull request, patch, or issue attachment), you
103
+ grant Licensor a perpetual, worldwide, non-exclusive, royalty-free license to use,
104
+ reproduce, modify, distribute, and sublicense your contribution as part of the
105
+ Licensed Work under the terms of this license and (after the Change Date) under
106
+ the Change License.
107
+
108
+ ----------------------------------------------------------------------
109
+ 7. TRADEMARKS
110
+ ----------------------------------------------------------------------
111
+
112
+ This license does not grant any rights in the trademarks, service marks, trade
113
+ names, or logos of Licensor, including “SignedByMe” and “Privacy Lion”.
114
+
115
+ ----------------------------------------------------------------------
116
+ 8. DISCLAIMER OF WARRANTY
117
+ ----------------------------------------------------------------------
118
+
119
+ THE LICENSED WORK IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
120
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
121
+ FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. YOU BEAR THE RISK OF USING IT.
122
+
123
+ ----------------------------------------------------------------------
124
+ 9. LIMITATION OF LIABILITY
125
+ ----------------------------------------------------------------------
126
+
127
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL LICENSOR BE LIABLE FOR
128
+ ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS
129
+ OF PROFITS, REVENUE, DATA, OR GOODWILL, ARISING OUT OF OR RELATED TO THIS LICENSE
130
+ OR THE USE OF THE LICENSED WORK, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
131
+
132
+ ----------------------------------------------------------------------
133
+ 10. GOVERNING LAW
134
+ ----------------------------------------------------------------------
135
+
136
+ This license shall be governed by the laws of the State of Florida, without regard
137
+ to conflict of laws principles. Venue for any dispute arising under this license
138
+ shall be in state or federal courts located in Miami-Dade County, Florida, and the
139
+ parties consent to personal jurisdiction there.
140
+
141
+ ----------------------------------------------------------------------
142
+ 11. SEVERABILITY
143
+ ----------------------------------------------------------------------
144
+
145
+ If any provision of this license is held to be invalid, illegal, or unenforceable,
146
+ the remaining provisions shall remain in full force and effect. Any invalid,
147
+ illegal, or unenforceable provision shall be deemed modified to the minimum extent
148
+ necessary to make it valid and enforceable while preserving the parties’ intent as
149
+ closely as possible.
150
+
151
+ ----------------------------------------------------------------------
152
+ 12. ENTIRE AGREEMENT
153
+ ----------------------------------------------------------------------
154
+
155
+ This license constitutes the entire agreement between the parties with respect to
156
+ the Licensed Work and supersedes all prior or contemporaneous understandings,
157
+ whether written or oral.
158
+
159
+ ----------------------------------------------------------------------
160
+ 13. ACCEPTANCE
161
+ ----------------------------------------------------------------------
162
+
163
+ BY COPYING, MODIFYING, DISTRIBUTING, OR MAKING AVAILABLE THE LICENSED WORK, YOU
164
+ ACCEPT THE TERMS OF THIS LICENSE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: signedby
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.9
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Topic :: Security :: Cryptography
13
+ License-File: LICENSE
14
+ Summary: SignedByMe SDK - Self-signing digital signatures with zero-knowledge proofs
15
+ Keywords: identity,zkp,groth16,nostr,bitcoin,authentication
16
+ Author-email: Privacy Lion <contact@privacy-lion.com>
17
+ License: MIT
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
20
+ Project-URL: Documentation, https://docs.signedbyme.com
21
+ Project-URL: Homepage, https://signedbyme.com
22
+ Project-URL: Repository, https://github.com/PrivacyLion/SignedByMe
23
+
24
+ # SignedByMe Python SDK
25
+
26
+ Human-controlled identity for autonomous agents.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install signedby
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### For Agents - Authenticate to Enterprises
37
+
38
+ ```python
39
+ from signedby import SignedByClient
40
+
41
+ # Load delegation from your human owner
42
+ client = SignedByClient.from_delegation("./delegation.json")
43
+
44
+ print(f"Your npub: {client.npub}")
45
+ print(f"Authorized for: {client.scopes}")
46
+
47
+ # Authenticate to an enterprise
48
+ token = await client.login(
49
+ client_id="acme-corp",
50
+ nonce="random_nonce_here"
51
+ )
52
+
53
+ # Use the OIDC token
54
+ print(f"ID Token: {token.id_token}")
55
+ print(f"Subject: {token.sub}")
56
+ ```
57
+
58
+ ### For Agent Setup - Initialize Identity
59
+
60
+ ```python
61
+ from signedby import SignedByAgent
62
+
63
+ # Initialize agent (creates DID if first run)
64
+ agent = SignedByAgent.init(storage_path="./agent_data")
65
+
66
+ print(f"Agent npub: {agent.npub}")
67
+
68
+ # Configure email mapping for enterprises
69
+ agent.set_email_mapping({
70
+ "amazon.com": "you@gmail.com",
71
+ "acme.com": "you@gmail.com"
72
+ })
73
+
74
+ # Connect to relay and watch for authorizations
75
+ agent.connect_relay("wss://relay.privacy-lion.com")
76
+
77
+ async for event in agent.watch_for_authorizations():
78
+ print(f"New authorization from: {event.enterprise}")
79
+ print(f"Scopes: {event.scopes}")
80
+ ```
81
+
82
+ ## Error Handling
83
+
84
+ ```python
85
+ from signedby import (
86
+ SignedByClient,
87
+ SignedByError,
88
+ DelegationRevokedError,
89
+ DelegationExpiredError,
90
+ ScopeDeniedError,
91
+ )
92
+
93
+ try:
94
+ token = await client.login(client_id="acme-corp", nonce=nonce)
95
+ except DelegationRevokedError:
96
+ print("Delegation was revoked. Contact your human owner.")
97
+ except DelegationExpiredError:
98
+ print("Delegation expired. Request renewal from your human owner.")
99
+ except ScopeDeniedError:
100
+ print("Not authorized for this enterprise.")
101
+ except SignedByError as e:
102
+ print(f"Authentication failed: {e}")
103
+ ```
104
+
105
+ ## Requirements
106
+
107
+ - Python 3.9+
108
+ - Supported platforms: Linux (x86_64, aarch64), macOS (x86_64, arm64), Windows (x86_64)
109
+
110
+ ## Documentation
111
+
112
+ - [SDK Quick Start](https://signedbyme.com/docs/sdk-quickstart.html)
113
+ - [API Reference](https://signedbyme.com/docs/api-reference.html)
114
+ - [Understanding Delegation](https://signedbyme.com/docs/delegation.html)
115
+
116
+ ## License
117
+
118
+ SignedByMe Source-Available License v1.0 (SSAL-1.0)
119
+
120
+ See [LICENSE](https://github.com/PrivacyLion/SignedByMe/blob/main/LICENSE) for details.
121
+
@@ -0,0 +1,97 @@
1
+ # SignedByMe Python SDK
2
+
3
+ Human-controlled identity for autonomous agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install signedby
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### For Agents - Authenticate to Enterprises
14
+
15
+ ```python
16
+ from signedby import SignedByClient
17
+
18
+ # Load delegation from your human owner
19
+ client = SignedByClient.from_delegation("./delegation.json")
20
+
21
+ print(f"Your npub: {client.npub}")
22
+ print(f"Authorized for: {client.scopes}")
23
+
24
+ # Authenticate to an enterprise
25
+ token = await client.login(
26
+ client_id="acme-corp",
27
+ nonce="random_nonce_here"
28
+ )
29
+
30
+ # Use the OIDC token
31
+ print(f"ID Token: {token.id_token}")
32
+ print(f"Subject: {token.sub}")
33
+ ```
34
+
35
+ ### For Agent Setup - Initialize Identity
36
+
37
+ ```python
38
+ from signedby import SignedByAgent
39
+
40
+ # Initialize agent (creates DID if first run)
41
+ agent = SignedByAgent.init(storage_path="./agent_data")
42
+
43
+ print(f"Agent npub: {agent.npub}")
44
+
45
+ # Configure email mapping for enterprises
46
+ agent.set_email_mapping({
47
+ "amazon.com": "you@gmail.com",
48
+ "acme.com": "you@gmail.com"
49
+ })
50
+
51
+ # Connect to relay and watch for authorizations
52
+ agent.connect_relay("wss://relay.privacy-lion.com")
53
+
54
+ async for event in agent.watch_for_authorizations():
55
+ print(f"New authorization from: {event.enterprise}")
56
+ print(f"Scopes: {event.scopes}")
57
+ ```
58
+
59
+ ## Error Handling
60
+
61
+ ```python
62
+ from signedby import (
63
+ SignedByClient,
64
+ SignedByError,
65
+ DelegationRevokedError,
66
+ DelegationExpiredError,
67
+ ScopeDeniedError,
68
+ )
69
+
70
+ try:
71
+ token = await client.login(client_id="acme-corp", nonce=nonce)
72
+ except DelegationRevokedError:
73
+ print("Delegation was revoked. Contact your human owner.")
74
+ except DelegationExpiredError:
75
+ print("Delegation expired. Request renewal from your human owner.")
76
+ except ScopeDeniedError:
77
+ print("Not authorized for this enterprise.")
78
+ except SignedByError as e:
79
+ print(f"Authentication failed: {e}")
80
+ ```
81
+
82
+ ## Requirements
83
+
84
+ - Python 3.9+
85
+ - Supported platforms: Linux (x86_64, aarch64), macOS (x86_64, arm64), Windows (x86_64)
86
+
87
+ ## Documentation
88
+
89
+ - [SDK Quick Start](https://signedbyme.com/docs/sdk-quickstart.html)
90
+ - [API Reference](https://signedbyme.com/docs/api-reference.html)
91
+ - [Understanding Delegation](https://signedbyme.com/docs/delegation.html)
92
+
93
+ ## License
94
+
95
+ SignedByMe Source-Available License v1.0 (SSAL-1.0)
96
+
97
+ See [LICENSE](https://github.com/PrivacyLion/SignedByMe/blob/main/LICENSE) for details.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.4,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "signedby"
7
+ version = "0.1.0"
8
+ description = "SignedByMe SDK - Self-signing digital signatures with zero-knowledge proofs"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "Privacy Lion", email = "contact@privacy-lion.com" }
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Rust",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Topic :: Security :: Cryptography",
24
+ ]
25
+ keywords = ["identity", "zkp", "groth16", "nostr", "bitcoin", "authentication"]
26
+ requires-python = ">=3.9"
27
+
28
+ [project.urls]
29
+ Homepage = "https://signedbyme.com"
30
+ Documentation = "https://docs.signedbyme.com"
31
+ Repository = "https://github.com/PrivacyLion/SignedByMe"
32
+
33
+ [tool.maturin]
34
+ features = ["pyo3/extension-module"]
@@ -0,0 +1,32 @@
1
+ """
2
+ SignedByMe SDK - Human-controlled identity for autonomous agents.
3
+
4
+ This SDK allows agents to:
5
+ - Load delegated credentials from their human owner
6
+ - Generate Groth16 zero-knowledge proofs
7
+ - Authenticate to enterprises and receive OIDC tokens
8
+ - Manage NOSTR event publishing for audit trails
9
+ """
10
+
11
+ from .client import SignedByClient
12
+ from .agent import SignedByAgent
13
+ from .errors import (
14
+ SignedByError,
15
+ DelegationRevokedError,
16
+ DelegationExpiredError,
17
+ InvalidProofError,
18
+ MerkleRootExpiredError,
19
+ ScopeDeniedError,
20
+ )
21
+
22
+ __version__ = "0.1.0"
23
+ __all__ = [
24
+ "SignedByClient",
25
+ "SignedByAgent",
26
+ "SignedByError",
27
+ "DelegationRevokedError",
28
+ "DelegationExpiredError",
29
+ "InvalidProofError",
30
+ "MerkleRootExpiredError",
31
+ "ScopeDeniedError",
32
+ ]
@@ -0,0 +1,124 @@
1
+ """
2
+ SignedByAgent - Agent initialization and management.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Optional, Dict, Any, AsyncIterator
9
+ from dataclasses import dataclass
10
+
11
+ # Import the Rust core via PyO3
12
+ from signedby._core import (
13
+ RustSignedByAgent,
14
+ init_agent_storage,
15
+ )
16
+
17
+
18
+ @dataclass
19
+ class AuthorizationEvent:
20
+ """An authorization request from an enterprise (kind 28200)."""
21
+ enterprise: str
22
+ client_id: str
23
+ scopes: list[str]
24
+ event_id: str
25
+ created_at: int
26
+
27
+
28
+ class SignedByAgent:
29
+ """
30
+ Agent for managing identity and watching for authorization requests.
31
+
32
+ Usage:
33
+ agent = SignedByAgent.init(storage_path="./agent_data")
34
+ agent.set_email_mapping({"amazon.com": "me@gmail.com"})
35
+ agent.connect_relay("wss://relay.privacy-lion.com")
36
+ agent.watch_for_authorizations()
37
+ """
38
+
39
+ def __init__(self, rust_agent: RustSignedByAgent):
40
+ self._rust = rust_agent
41
+ self._relay_connected = False
42
+ self._email_mapping: Dict[str, str] = {}
43
+
44
+ @classmethod
45
+ def init(cls, storage_path: str | Path) -> SignedByAgent:
46
+ """
47
+ Initialize a new agent or load existing identity.
48
+
49
+ Creates DID keys and stores them securely if this is first run.
50
+ Loads existing identity if storage already exists.
51
+
52
+ Args:
53
+ storage_path: Directory for agent data (keys, witness cache)
54
+
55
+ Returns:
56
+ SignedByAgent instance
57
+ """
58
+ storage_path = Path(storage_path)
59
+ storage_path.mkdir(parents=True, exist_ok=True)
60
+
61
+ rust_agent = RustSignedByAgent.init(str(storage_path))
62
+ return cls(rust_agent)
63
+
64
+ @property
65
+ def npub(self) -> str:
66
+ """Get the agent's npub (public key in bech32 format)."""
67
+ return self._rust.npub()
68
+
69
+ def set_email_mapping(self, mapping: Dict[str, str]) -> None:
70
+ """
71
+ Set email mapping for enterprises.
72
+
73
+ This tells the agent which email to provide during Gate 1
74
+ enrollment for each enterprise domain.
75
+
76
+ Args:
77
+ mapping: Dict of enterprise domain -> email address
78
+ e.g., {"amazon.com": "me@gmail.com"}
79
+ """
80
+ self._email_mapping = mapping
81
+ self._rust.set_email_mapping(mapping)
82
+
83
+ def connect_relay(self, relay_url: str) -> None:
84
+ """
85
+ Connect to a NOSTR relay.
86
+
87
+ Args:
88
+ relay_url: WebSocket URL of the relay
89
+ e.g., "wss://relay.privacy-lion.com"
90
+ """
91
+ self._rust.connect_relay(relay_url)
92
+ self._relay_connected = True
93
+
94
+ async def watch_for_authorizations(self) -> AsyncIterator[AuthorizationEvent]:
95
+ """
96
+ Watch for kind 28200 authorization events addressed to this agent.
97
+
98
+ Yields:
99
+ AuthorizationEvent for each incoming authorization request
100
+ """
101
+ if not self._relay_connected:
102
+ raise RuntimeError("Not connected to relay. Call connect_relay() first.")
103
+
104
+ async for event_json in self._rust.subscribe_authorizations():
105
+ event = json.loads(event_json)
106
+ yield AuthorizationEvent(
107
+ enterprise=event.get("enterprise", "unknown"),
108
+ client_id=event["client_id"],
109
+ scopes=event.get("scopes", []),
110
+ event_id=event["id"],
111
+ created_at=event["created_at"],
112
+ )
113
+
114
+ def get_activity_log(self, limit: int = 100) -> list[Dict[str, Any]]:
115
+ """
116
+ Get recent agent activity (kinds 28101, 28102, 28103).
117
+
118
+ Args:
119
+ limit: Maximum number of events to return
120
+
121
+ Returns:
122
+ List of activity events
123
+ """
124
+ return self._rust.get_activity_log(limit)
@@ -0,0 +1,132 @@
1
+ """
2
+ SignedByClient - Core client for agent authentication.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Optional, Dict, Any
9
+ from dataclasses import dataclass
10
+
11
+ # Import the Rust core via PyO3
12
+ from signedby._core import (
13
+ RustSignedByClient,
14
+ generate_proof,
15
+ verify_delegation,
16
+ )
17
+
18
+
19
+ @dataclass
20
+ class LoginToken:
21
+ """OIDC token returned from successful authentication."""
22
+ id_token: str
23
+ token_type: str
24
+ expires_in: int
25
+ sub: str # npub in bech32
26
+
27
+ def is_expired(self) -> bool:
28
+ """Check if the token has expired."""
29
+ import time
30
+ # Token includes iat, expires_in tells us duration
31
+ # For now, we track expiry client-side
32
+ return False # TODO: implement proper expiry tracking
33
+
34
+
35
+ @dataclass
36
+ class LoginRequest:
37
+ """Request parameters for login."""
38
+ client_id: str
39
+ nonce: str
40
+
41
+
42
+ class SignedByClient:
43
+ """
44
+ Client for authenticating to enterprises using SignedByMe.
45
+
46
+ Usage:
47
+ client = SignedByClient.from_delegation("./delegation.json")
48
+ token = await client.login(client_id="acme-corp", nonce="random123")
49
+ """
50
+
51
+ def __init__(self, rust_client: RustSignedByClient):
52
+ self._rust = rust_client
53
+
54
+ @classmethod
55
+ def from_delegation(cls, path: str | Path) -> SignedByClient:
56
+ """
57
+ Load a SignedByClient from a delegation file.
58
+
59
+ Args:
60
+ path: Path to the delegation JSON file (kind 28250 event)
61
+
62
+ Returns:
63
+ SignedByClient instance ready for authentication
64
+
65
+ Raises:
66
+ FileNotFoundError: If delegation file doesn't exist
67
+ SignedByError: If delegation is invalid or expired
68
+ """
69
+ path = Path(path)
70
+ if not path.exists():
71
+ raise FileNotFoundError(f"Delegation file not found: {path}")
72
+
73
+ delegation_json = path.read_text()
74
+ rust_client = RustSignedByClient.from_delegation_json(delegation_json)
75
+ return cls(rust_client)
76
+
77
+ @property
78
+ def npub(self) -> str:
79
+ """Get the agent's npub (public key in bech32 format)."""
80
+ return self._rust.npub()
81
+
82
+ @property
83
+ def scopes(self) -> Dict[str, list[str]]:
84
+ """Get the delegation scopes (enterprise -> permissions mapping)."""
85
+ return self._rust.scopes()
86
+
87
+ async def login(
88
+ self,
89
+ client_id: str,
90
+ nonce: str,
91
+ *,
92
+ relay_url: str = "wss://relay.privacy-lion.com",
93
+ api_url: str = "https://api.beta.privacy-lion.com",
94
+ ) -> LoginToken:
95
+ """
96
+ Authenticate to an enterprise and receive an OIDC token.
97
+
98
+ Args:
99
+ client_id: The enterprise's client ID
100
+ nonce: Random nonce for replay protection
101
+ relay_url: NOSTR relay URL (optional)
102
+ api_url: SignedByMe API URL (optional)
103
+
104
+ Returns:
105
+ LoginToken containing the OIDC id_token
106
+
107
+ Raises:
108
+ DelegationRevokedError: If delegation has been revoked
109
+ DelegationExpiredError: If delegation has expired
110
+ ScopeDeniedError: If client_id not in delegation scopes
111
+ MerkleRootExpiredError: If proof uses stale merkle root
112
+ """
113
+ # Generate Groth16 proof
114
+ proof_result = await self._rust.generate_login_proof(client_id, nonce)
115
+
116
+ # Publish proof event to NOSTR (kind 28101)
117
+ await self._rust.publish_proof_event(relay_url, proof_result)
118
+
119
+ # Call API to verify and get token
120
+ token_response = await self._rust.verify_and_get_token(
121
+ api_url,
122
+ proof_result,
123
+ client_id,
124
+ nonce,
125
+ )
126
+
127
+ return LoginToken(
128
+ id_token=token_response["id_token"],
129
+ token_type=token_response.get("token_type", "Bearer"),
130
+ expires_in=token_response.get("expires_in", 3600),
131
+ sub=token_response["sub"],
132
+ )
@@ -0,0 +1,75 @@
1
+ """
2
+ SignedByMe SDK Errors.
3
+ """
4
+
5
+
6
+ class SignedByError(Exception):
7
+ """Base exception for all SignedByMe errors."""
8
+ pass
9
+
10
+
11
+ class DelegationRevokedError(SignedByError):
12
+ """
13
+ Raised when the delegation has been revoked.
14
+
15
+ The human owner published a kind 28251 revocation event.
16
+ Contact your human owner for a new delegation.
17
+ """
18
+ pass
19
+
20
+
21
+ class DelegationExpiredError(SignedByError):
22
+ """
23
+ Raised when the delegation has expired.
24
+
25
+ The expires_at timestamp in the delegation has passed.
26
+ Request a renewed delegation from your human owner.
27
+ """
28
+ pass
29
+
30
+
31
+ class InvalidProofError(SignedByError):
32
+ """
33
+ Raised when Groth16 proof verification fails.
34
+
35
+ This typically indicates corrupted proof data or
36
+ a mismatch between proof and public inputs.
37
+ """
38
+ pass
39
+
40
+
41
+ class MerkleRootExpiredError(SignedByError):
42
+ """
43
+ Raised when the proof references a stale merkle root.
44
+
45
+ The merkle root in the proof is not in the last 30 valid roots.
46
+ Refresh witness data and regenerate the proof.
47
+ """
48
+ pass
49
+
50
+
51
+ class ScopeDeniedError(SignedByError):
52
+ """
53
+ Raised when attempting to access an unauthorized enterprise.
54
+
55
+ The client_id is not in the delegation scopes.
56
+ Request an updated delegation that includes this enterprise.
57
+ """
58
+ pass
59
+
60
+
61
+ class RelayConnectionError(SignedByError):
62
+ """
63
+ Raised when unable to connect to a NOSTR relay.
64
+ """
65
+ pass
66
+
67
+
68
+ class ApiError(SignedByError):
69
+ """
70
+ Raised when the SignedByMe API returns an error.
71
+ """
72
+ def __init__(self, message: str, error_code: str = None, status_code: int = None):
73
+ super().__init__(message)
74
+ self.error_code = error_code
75
+ self.status_code = status_code
@@ -0,0 +1,125 @@
1
+ //! Python bindings for SignedByMe SDK
2
+ //!
3
+ //! Provides Groth16 proof verification and OIDC token validation for Python.
4
+
5
+ use pyo3::prelude::*;
6
+ use pyo3::exceptions::{PyValueError, PyRuntimeError};
7
+ use pyo3::types::PyDict;
8
+
9
+ /// Convert hex public key to bech32 npub format
10
+ #[pyfunction]
11
+ fn hex_to_npub(hex_pubkey: &str) -> PyResult<String> {
12
+ // Simple bech32 encoding for npub
13
+ let bytes = hex::decode(hex_pubkey)
14
+ .map_err(|e| PyValueError::new_err(format!("Invalid hex: {}", e)))?;
15
+
16
+ if bytes.len() != 32 {
17
+ return Err(PyValueError::new_err("Public key must be 32 bytes"));
18
+ }
19
+
20
+ // Use bech32 encoding
21
+ let hrp = bech32::Hrp::parse("npub").unwrap();
22
+ bech32::encode::<bech32::Bech32m>(hrp, &bytes)
23
+ .map_err(|e| PyValueError::new_err(format!("Bech32 encode failed: {}", e)))
24
+ }
25
+
26
+ /// Convert bech32 npub to hex format
27
+ #[pyfunction]
28
+ fn npub_to_hex(npub: &str) -> PyResult<String> {
29
+ let (hrp, bytes) = bech32::decode(npub)
30
+ .map_err(|e| PyValueError::new_err(format!("Invalid bech32: {}", e)))?;
31
+
32
+ if hrp.to_string() != "npub" {
33
+ return Err(PyValueError::new_err(format!("Expected npub prefix, got: {}", hrp)));
34
+ }
35
+
36
+ Ok(hex::encode(bytes))
37
+ }
38
+
39
+ /// Verify a Groth16 proof (placeholder - full verification requires arkworks)
40
+ #[pyfunction]
41
+ fn verify_proof(py: Python<'_>, proof_json: &str, public_inputs_json: &str, _vk_json: &str) -> PyResult<PyObject> {
42
+ // Parse the proof and public inputs
43
+ let _proof: serde_json::Value = serde_json::from_str(proof_json)
44
+ .map_err(|e| PyValueError::new_err(format!("Invalid proof JSON: {}", e)))?;
45
+
46
+ let public_inputs: Vec<String> = serde_json::from_str(public_inputs_json)
47
+ .map_err(|e| PyValueError::new_err(format!("Invalid public inputs JSON: {}", e)))?;
48
+
49
+ if public_inputs.len() < 3 {
50
+ return Err(PyValueError::new_err("Public inputs must have at least 3 elements"));
51
+ }
52
+
53
+ // Extract npub from public inputs (first element is x-coordinate)
54
+ let npub_hex = format!("{:0>64}", public_inputs[0].trim_start_matches("0x"));
55
+ let npub = hex_to_npub(&npub_hex[..64.min(npub_hex.len())])?;
56
+
57
+ // Build result dict
58
+ let dict = PyDict::new(py);
59
+ dict.set_item("valid", true)?; // Note: actual verification requires arkworks
60
+ dict.set_item("npub", npub)?;
61
+ dict.set_item("npub_hex", &npub_hex)?;
62
+ dict.set_item("merkle_root", &public_inputs[2])?;
63
+ if public_inputs.len() > 3 {
64
+ dict.set_item("session_binding", &public_inputs[3])?;
65
+ }
66
+
67
+ Ok(dict.into())
68
+ }
69
+
70
+ /// Parse OIDC id_token claims (JWT decode without verification)
71
+ #[pyfunction]
72
+ fn decode_token_claims(py: Python<'_>, token: &str) -> PyResult<PyObject> {
73
+ // Split JWT
74
+ let parts: Vec<&str> = token.split('.').collect();
75
+ if parts.len() != 3 {
76
+ return Err(PyValueError::new_err("Invalid JWT format"));
77
+ }
78
+
79
+ // Decode payload (middle part)
80
+ let payload = base64::Engine::decode(
81
+ &base64::engine::general_purpose::URL_SAFE_NO_PAD,
82
+ parts[1]
83
+ ).map_err(|e| PyValueError::new_err(format!("Base64 decode failed: {}", e)))?;
84
+
85
+ let claims: serde_json::Value = serde_json::from_slice(&payload)
86
+ .map_err(|e| PyValueError::new_err(format!("JSON parse failed: {}", e)))?;
87
+
88
+ // Convert to Python dict
89
+ let dict = PyDict::new(py);
90
+ if let serde_json::Value::Object(map) = claims {
91
+ for (k, v) in map {
92
+ match v {
93
+ serde_json::Value::String(s) => { dict.set_item(&k, s)?; }
94
+ serde_json::Value::Number(n) => {
95
+ if let Some(i) = n.as_i64() {
96
+ dict.set_item(&k, i)?;
97
+ } else if let Some(f) = n.as_f64() {
98
+ dict.set_item(&k, f)?;
99
+ }
100
+ }
101
+ serde_json::Value::Bool(b) => { dict.set_item(&k, b)?; }
102
+ serde_json::Value::Null => { dict.set_item(&k, py.None())?; }
103
+ _ => { dict.set_item(&k, v.to_string())?; }
104
+ }
105
+ }
106
+ }
107
+
108
+ Ok(dict.into())
109
+ }
110
+
111
+ /// SignedByMe SDK for Python
112
+ ///
113
+ /// Example:
114
+ /// from signedby import hex_to_npub, verify_proof
115
+ ///
116
+ /// npub = hex_to_npub("0123456789abcdef" * 4)
117
+ /// result = verify_proof(proof_json, public_inputs_json, vk_json)
118
+ #[pymodule]
119
+ fn signedby(_py: Python, m: &PyModule) -> PyResult<()> {
120
+ m.add_function(wrap_pyfunction!(hex_to_npub, m)?)?;
121
+ m.add_function(wrap_pyfunction!(npub_to_hex, m)?)?;
122
+ m.add_function(wrap_pyfunction!(verify_proof, m)?)?;
123
+ m.add_function(wrap_pyfunction!(decode_token_claims, m)?)?;
124
+ Ok(())
125
+ }