atproto libraries implementation in ocaml

crypto: switch JWT JSON to simdjsont

Changed files
+38 -28
.beads
lib
crypto
+1 -1
.beads/issues.jsonl
··· 28 28 {"id":"atproto-52","title":"Implement Lexicon validation","description":"Implement Lexicon-based validation for AT Protocol data. This validates records and API payloads against schemas.","design":"## Module Structure\n\n```ocaml\n(* atproto-lexicon/lib/validator.ml *)\ntype validation_error = {\n path: string list;\n message: string;\n}\n\nval validate_record :\n registry:Registry.t -\u003e\n nsid:Nsid.t -\u003e\n value:Dag_cbor.value -\u003e\n (unit, validation_error list) result\n\nval validate_xrpc_params :\n registry:Registry.t -\u003e\n nsid:Nsid.t -\u003e\n params:(string * string) list -\u003e\n (unit, validation_error list) result\n\nval validate_xrpc_input :\n registry:Registry.t -\u003e\n nsid:Nsid.t -\u003e\n input:Jsont.json -\u003e\n (unit, validation_error list) result\n\nval validate_xrpc_output :\n registry:Registry.t -\u003e\n nsid:Nsid.t -\u003e\n output:Jsont.json -\u003e\n (unit, validation_error list) result\n```\n\n## Constraint Types\n\n- **String**: minLength, maxLength, minGraphemes, maxGraphemes, format, enum, const\n- **Integer**: minimum, maximum, enum, const\n- **Bytes**: minLength, maxLength\n- **Array**: minLength, maxLength, items type\n- **Blob**: maxSize, accept (MIME types)\n- **Union**: open/closed, refs\n\n## Format Validators (Parser-based, NO REGEX)\n\nEach format has a dedicated parser module:\n\n```ocaml\n(* atproto-lexicon/lib/formats.ml *)\n\nlet validate_did s = Did.of_string s |\u003e Result.is_ok\nlet validate_handle s = Handle.of_string s |\u003e Result.is_ok\nlet validate_nsid s = Nsid.of_string s |\u003e Result.is_ok\nlet validate_tid s = Tid.of_string s |\u003e Result.is_ok\nlet validate_cid s = Cid.of_string s |\u003e Result.is_ok\nlet validate_at_uri s = At_uri.of_string s |\u003e Result.is_ok\nlet validate_at_identifier s = \n Did.of_string s |\u003e Result.is_ok || Handle.of_string s |\u003e Result.is_ok\nlet validate_record_key s = Record_key.of_string s |\u003e Result.is_ok\n\nlet validate_datetime s =\n (* Hand-written RFC-3339 parser *)\n parse_datetime s |\u003e Result.is_ok\n\nlet validate_language s =\n (* BCP-47 language tag parser *)\n parse_language_tag s |\u003e Result.is_ok\n\nlet validate_uri s =\n (* RFC-3986 URI parser *)\n Uri.of_string s |\u003e Option.is_some\n```\n\n## Dependencies\n- atproto-lexicon (schema)\n- atproto-syntax (format validators)\n- jsont","acceptance_criteria":"- Validate records against schemas\n- Validate XRPC params, input, output\n- Proper error messages with paths\n- All constraint types supported\n- All record-data interop tests pass","status":"closed","priority":1,"issue_type":"feature","assignee":"claude","created_at":"2025-12-28T00:11:39.125440686+01:00","updated_at":"2025-12-28T10:25:46.671434007+01:00","closed_at":"2025-12-28T10:25:46.671434007+01:00","labels":["application","lexicon"],"dependencies":[{"issue_id":"atproto-52","depends_on_id":"atproto-50","type":"parent-child","created_at":"2025-12-28T00:12:05.375287273+01:00","created_by":"daemon"},{"issue_id":"atproto-52","depends_on_id":"atproto-51","type":"blocks","created_at":"2025-12-28T00:12:09.479940241+01:00","created_by":"daemon"}]} 29 29 {"id":"atproto-53","title":"Implement Lexicon code generation","description":"Implement code generation from Lexicon schemas to OCaml types and API bindings.","design":"## Module Structure\n\n```ocaml\n(* atproto-lexicon-gen/lib/codegen.ml *)\ntype config = {\n output_dir: string;\n module_prefix: string;\n}\n\nval generate_types : config:config -\u003e lexicon:Lexicon.t -\u003e unit\nval generate_client : config:config -\u003e lexicons:Lexicon.t list -\u003e unit\n```\n\n## Generated Code Example\n\nInput Lexicon:\n```json\n{\n \"id\": \"app.bsky.feed.post\",\n \"defs\": {\n \"main\": {\n \"type\": \"record\",\n \"record\": {\n \"type\": \"object\",\n \"properties\": {\n \"text\": { \"type\": \"string\", \"maxGraphemes\": 300 },\n \"createdAt\": { \"type\": \"string\", \"format\": \"datetime\" }\n }\n }\n }\n }\n}\n```\n\nGenerated OCaml:\n```ocaml\nmodule App_bsky_feed_post = struct\n type t = {\n text: string;\n created_at: Ptime.t;\n }\n \n let jsont : t Jsont.t =\n Jsont.obj \"app.bsky.feed.post\" @@ fun o -\u003e\n let text = Jsont.obj_mem o \"text\" Jsont.string in\n let created_at = Jsont.obj_mem o \"createdAt\" Datetime.jsont in\n Jsont.obj_finish o { text; created_at }\n \n val to_dag_cbor : t -\u003e Dag_cbor.value\n val of_dag_cbor : Dag_cbor.value -\u003e (t, error) result\nend\n```\n\n## CLI Tool\n\n```bash\natproto-lexicon-gen --input lexicons/ --output lib/generated/\n```\n\n## Dependencies\n- atproto-lexicon\n- jsont","acceptance_criteria":"- Generate OCaml types from Lexicon schemas\n- Generate encoders/decoders\n- Type-safe API bindings\n- CLI tool for code generation","status":"closed","priority":2,"issue_type":"feature","assignee":"claude","created_at":"2025-12-28T00:11:47.861552784+01:00","updated_at":"2025-12-28T11:28:03.226633204+01:00","closed_at":"2025-12-28T11:28:03.226633204+01:00","labels":["application","codegen"],"dependencies":[{"issue_id":"atproto-53","depends_on_id":"atproto-50","type":"parent-child","created_at":"2025-12-28T00:12:06.539440409+01:00","created_by":"daemon"},{"issue_id":"atproto-53","depends_on_id":"atproto-51","type":"blocks","created_at":"2025-12-28T00:12:11.189125052+01:00","created_by":"daemon"}]} 30 30 {"id":"atproto-54","title":"Implement high-level API client","description":"Implement high-level API client for AT Protocol / Bluesky. This provides a user-friendly interface for common operations.","design":"## Module Structure\n\n```ocaml\n(* atproto-api/lib/agent.ml *)\ntype t\n\nval create : pds:Uri.t -\u003e t\n\n(* Authentication *)\nval login : t -\u003e identifier:string -\u003e password:string -\u003e (t, error) result\nval login_oauth : t -\u003e tokens:Oauth.tokens -\u003e t\nval refresh_session : t -\u003e (t, error) result\n\n(* Profile *)\nval get_profile : t -\u003e actor:string -\u003e (profile, error) result\nval update_profile : t -\u003e display_name:string option -\u003e ... -\u003e (unit, error) result\n\n(* Posts *)\nval create_post : t -\u003e text:string -\u003e ?reply:reply_ref -\u003e ... -\u003e (post_ref, error) result\nval delete_post : t -\u003e uri:At_uri.t -\u003e (unit, error) result\n\n(* Social *)\nval like : t -\u003e uri:At_uri.t -\u003e cid:Cid.t -\u003e (like_ref, error) result\nval follow : t -\u003e did:Did.t -\u003e (follow_ref, error) result\nval unfollow : t -\u003e uri:At_uri.t -\u003e (unit, error) result\n\n(* Feed *)\nval get_timeline : t -\u003e ?cursor:string -\u003e ?limit:int -\u003e (timeline, error) result\nval get_author_feed : t -\u003e actor:string -\u003e ... -\u003e (feed, error) result\n\n(* atproto-api/lib/richtext.ml *)\ntype t\n\nval create : string -\u003e t\nval detect_facets : t -\u003e t (* auto-detect mentions, links *)\nval add_mention : t -\u003e start:int -\u003e end_:int -\u003e did:Did.t -\u003e t\nval add_link : t -\u003e start:int -\u003e end_:int -\u003e uri:Uri.t -\u003e t\nval to_post_record : t -\u003e Dag_cbor.value\n```\n\n## Jsont Codecs for API Types\n\n```ocaml\nlet profile_jsont : profile Jsont.t =\n Jsont.obj \"profile\" @@ fun o -\u003e\n let did = Jsont.obj_mem o \"did\" did_jsont in\n let handle = Jsont.obj_mem o \"handle\" handle_jsont in\n let display_name = Jsont.obj_mem o \"displayName\" ~opt:true Jsont.string in\n let description = Jsont.obj_mem o \"description\" ~opt:true Jsont.string in\n let avatar = Jsont.obj_mem o \"avatar\" ~opt:true Jsont.string in\n let followers_count = Jsont.obj_mem o \"followersCount\" ~opt:true Jsont.int in\n let follows_count = Jsont.obj_mem o \"followsCount\" ~opt:true Jsont.int in\n let posts_count = Jsont.obj_mem o \"postsCount\" ~opt:true Jsont.int in\n Jsont.obj_finish o { did; handle; display_name; description; avatar; \n followers_count; follows_count; posts_count }\n\nlet facet_jsont : facet Jsont.t =\n Jsont.obj \"facet\" @@ fun o -\u003e\n let index = Jsont.obj_mem o \"index\" byte_slice_jsont in\n let features = Jsont.obj_mem o \"features\" (Jsont.list facet_feature_jsont) in\n Jsont.obj_finish o { index; features }\n```\n\n## RichText Facets\n\n```json\n{\n \"text\": \"Hello @alice.bsky.social!\",\n \"facets\": [\n {\n \"index\": { \"byteStart\": 6, \"byteEnd\": 25 },\n \"features\": [\n { \"$type\": \"app.bsky.richtext.facet#mention\", \"did\": \"did:plc:...\" }\n ]\n }\n ]\n}\n```\n\n## Dependencies\n- atproto-xrpc\n- atproto-identity\n- atproto-repo\n- jsont","acceptance_criteria":"- Session management (login, logout, refresh)\n- Common operations (post, like, follow, etc.)\n- RichText handling (mentions, links, facets)\n- Timeline and feed fetching\n- Profile operations","status":"closed","priority":2,"issue_type":"feature","assignee":"claude","created_at":"2025-12-28T00:12:00.736309435+01:00","updated_at":"2025-12-28T11:47:47.071271001+01:00","closed_at":"2025-12-28T11:47:47.071271001+01:00","labels":["api","application"],"dependencies":[{"issue_id":"atproto-54","depends_on_id":"atproto-50","type":"parent-child","created_at":"2025-12-28T00:12:07.636789403+01:00","created_by":"daemon"},{"issue_id":"atproto-54","depends_on_id":"atproto-41","type":"blocks","created_at":"2025-12-28T00:12:12.376875324+01:00","created_by":"daemon"},{"issue_id":"atproto-54","depends_on_id":"atproto-33","type":"blocks","created_at":"2025-12-28T00:12:13.060557136+01:00","created_by":"daemon"},{"issue_id":"atproto-54","depends_on_id":"atproto-25","type":"blocks","created_at":"2025-12-28T00:12:13.934360048+01:00","created_by":"daemon"}]} 31 - {"id":"atproto-5l1","title":"Refactor JSON to simdjsont (replace yojson/jsont)","description":"","notes":"Proceeding with simdjsont 0.1.0 migration. Plan: introduce atproto_json wrapper using Simdjsont.Json.t + Simdjsont.Codec; migrate public APIs off Yojson.*.t; update dune/opam deps; keep tests green incrementally.","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2026-01-01T18:06:10.17746938+01:00","updated_at":"2026-01-01T19:00:19.875102367+01:00","dependencies":[{"issue_id":"atproto-5l1","depends_on_id":"atproto-dqs","type":"blocks","created_at":"2026-01-01T18:06:39.648046004+01:00","created_by":"daemon"}]} 31 + {"id":"atproto-5l1","title":"Refactor JSON to simdjsont (replace yojson/jsont)","description":"","notes":"Next steps: migrate lib/crypto/jwt.ml off Yojson.Safe to Atproto_json/Simdjsont.Json (header_to_json/claims_to_json + *_of_json + create/decode_unverified); add atproto-json to lib/crypto/dune; run dune runtest test/crypto + full runtest; then continue with other Yojson hotspots (xrpc client/server, identity did_resolver, sync firehose, ipld blob).","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2026-01-01T18:06:10.17746938+01:00","updated_at":"2026-01-01T19:32:47.437650903+01:00","dependencies":[{"issue_id":"atproto-5l1","depends_on_id":"atproto-dqs","type":"blocks","created_at":"2026-01-01T18:06:39.648046004+01:00","created_by":"daemon"}]} 32 32 {"id":"atproto-60","title":"Implement effects-based I/O abstraction","description":"Implement the effects-based I/O abstraction layer that makes all libraries runtime-agnostic.","design":"## Module Structure\n\n```ocaml\n(* atproto-effects/lib/effects.ml *)\n\n(* HTTP effects *)\ntype http_request = {\n method_: [ `GET | `POST | `PUT | `DELETE ];\n uri: Uri.t;\n headers: (string * string) list;\n body: string option;\n}\n\ntype http_response = {\n status: int;\n headers: (string * string) list;\n body: string;\n}\n\ntype _ Effect.t +=\n | Http_request : http_request -\u003e http_response Effect.t\n\n(* DNS effects *)\ntype _ Effect.t +=\n | Dns_txt : string -\u003e string list Effect.t\n | Dns_a : string -\u003e string list Effect.t\n\n(* Time effects *)\ntype _ Effect.t +=\n | Now : Ptime.t Effect.t\n | Sleep : float -\u003e unit Effect.t\n\n(* Random effects *)\ntype _ Effect.t +=\n | Random_bytes : int -\u003e bytes Effect.t\n\n(* atproto-effects-eio/lib/handler.ml *)\nval run : (unit -\u003e 'a) -\u003e 'a\n```\n\n## Handler Example (eio)\n\n```ocaml\nlet run f =\n Effect.Deep.match_ f ()\n {\n retc = Fun.id;\n exnc = raise;\n effc = fun (type a) (e : a Effect.t) -\u003e\n match e with\n | Http_request req -\u003e\n Some (fun (k : (a, _) continuation) -\u003e\n let resp = Eio_client.request req in\n continue k resp)\n | Dns_txt domain -\u003e\n Some (fun k -\u003e\n let records = Eio_dns.txt domain in\n continue k records)\n | _ -\u003e None\n }\n```\n\n## Dependencies\n- eio (for testing handler)","acceptance_criteria":"- Effect types for HTTP, DNS, time, random\n- eio-based handler for testing\n- Handler composition utilities\n- Performance benchmarks","status":"closed","priority":1,"issue_type":"feature","assignee":"claude","created_at":"2025-12-28T00:12:29.021401617+01:00","updated_at":"2025-12-28T11:57:08.264086142+01:00","closed_at":"2025-12-28T11:57:08.264086142+01:00","labels":["effects","infrastructure"],"dependencies":[{"issue_id":"atproto-60","depends_on_id":"atproto-1","type":"parent-child","created_at":"2025-12-28T00:12:55.467983208+01:00","created_by":"daemon"}]} 33 33 {"id":"atproto-61","title":"Set up interoperability test suite","description":"Set up and run the AT Protocol interoperability tests from bluesky-social/atproto-interop-tests.","design":"## Test Structure\n\n```\ntest/\n├── interop/\n│ ├── syntax_test.ml # Handle, DID, NSID, TID, etc.\n│ ├── crypto_test.ml # Signatures, did:key\n│ ├── data_model_test.ml # DAG-CBOR, CID\n│ ├── mst_test.ml # Key heights, tree structure\n│ ├── lexicon_test.ml # Schema and record validation\n│ └── firehose_test.ml # Commit proofs\n├── fixtures/ # Cloned from atproto-interop-tests\n└── dune\n```\n\n## Test Approach\n\n1. Clone test vectors from GitHub\n2. Parse JSON fixtures using jsont\n3. Parse text fixtures line by line\n4. Run each test case\n5. Compare output to expected values\n\n## Example Test\n\n```ocaml\nlet load_json_fixtures path =\n let json = Jsont.of_file path in\n Jsont.decode (Jsont.list fixture_jsont) json\n\nlet%test \"handle_syntax_valid\" =\n let fixtures = load_lines \"fixtures/syntax/handle_syntax_valid.txt\" in\n List.for_all (fun line -\u003e\n match Handle.of_string line with\n | Ok _ -\u003e true\n | Error _ -\u003e false\n ) fixtures\n\nlet%test \"handle_syntax_invalid\" =\n let fixtures = load_lines \"fixtures/syntax/handle_syntax_invalid.txt\" in\n List.for_all (fun line -\u003e\n match Handle.of_string line with\n | Ok _ -\u003e false\n | Error _ -\u003e true\n ) fixtures\n\nlet%test \"crypto_signature_fixtures\" =\n let fixtures = load_json_fixtures \"fixtures/crypto/signature-fixtures.json\" in\n List.for_all (fun fixture -\u003e\n let message = Base64.decode fixture.message_base64 in\n let signature = Base64.decode fixture.signature_base64 in\n let key = Did_key.of_string fixture.public_key_did in\n let result = Crypto.verify key message signature in\n result = fixture.valid_signature\n ) fixtures\n```\n\n## Dependencies\n- alcotest or ounit2\n- jsont","acceptance_criteria":"- All syntax interop tests pass\n- All crypto interop tests pass\n- All data-model interop tests pass\n- All MST interop tests pass\n- All lexicon interop tests pass\n- All firehose interop tests pass","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T00:12:40.553908313+01:00","updated_at":"2025-12-28T13:25:34.614867702+01:00","closed_at":"2025-12-28T13:25:34.614867702+01:00","labels":["conformance","testing"],"dependencies":[{"issue_id":"atproto-61","depends_on_id":"atproto-1","type":"parent-child","created_at":"2025-12-28T00:12:56.180809368+01:00","created_by":"daemon"}]} 34 34 {"id":"atproto-62","title":"Set up monorepo package structure","description":"Set up the monorepo structure for multiple opam packages within a single repository.","design":"## Repository Structure\n\n```\natproto/\n├── dune-project # Root with all packages\n├── packages/\n│ ├── atproto-syntax/\n│ │ ├── lib/\n│ │ │ ├── dune\n│ │ │ └── *.ml\n│ │ ├── test/\n│ │ │ ├── dune\n│ │ │ └── *_test.ml\n│ │ └── atproto-syntax.opam\n│ ├── atproto-crypto/\n│ ├── atproto-multibase/\n│ ├── atproto-ipld/\n│ ├── atproto-mst/\n│ ├── atproto-repo/\n│ ├── atproto-identity/\n│ ├── atproto-xrpc/\n│ ├── atproto-sync/\n│ ├── atproto-lexicon/\n│ ├── atproto-lexicon-gen/\n│ ├── atproto-api/\n│ └── atproto-effects/\n├── examples/\n│ ├── simple_client/\n│ └── firehose_consumer/\n└── interop-tests/\n```\n\n## dune-project\n\n```lisp\n(lang dune 3.20)\n(name atproto)\n(generate_opam_files true)\n\n(package\n (name atproto-syntax)\n (synopsis \"AT Protocol identifier syntax parsing\")\n (depends\n (ocaml (\u003e= 5.4))\n re\n ptime))\n\n(package\n (name atproto-crypto)\n ...)\n```\n\n## CI (.github/workflows/ci.yml)\n\n- OCaml 5.4 matrix\n- Build all packages\n- Run all tests\n- Run interop tests","acceptance_criteria":"- Multi-package dune-project structure\n- Separate opam files per package\n- CI pipeline for building and testing\n- Documentation generation setup","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T00:12:50.547102438+01:00","updated_at":"2025-12-28T11:57:18.856810633+01:00","closed_at":"2025-12-28T11:57:18.856810633+01:00","labels":["infrastructure","setup"],"dependencies":[{"issue_id":"atproto-62","depends_on_id":"atproto-1","type":"parent-child","created_at":"2025-12-28T00:12:57.015938611+01:00","created_by":"daemon"}]}
+1 -1
lib/crypto/dune
··· 1 1 (library 2 2 (name atproto_crypto) 3 3 (public_name atproto-crypto) 4 - (libraries atproto_multibase mirage-crypto-ec mirage-crypto-rng digestif zarith base64 yojson) 4 + (libraries atproto_multibase mirage-crypto-ec mirage-crypto-rng digestif zarith base64 yojson simdjsont) 5 5 (preprocess no_preprocessing))
+36 -26
lib/crypto/jwt.ml
··· 84 84 | "ES256K" -> Ok ES256K 85 85 | alg -> Error (`Unsupported_algorithm alg) 86 86 87 - let header_to_json (h : header) : Yojson.Safe.t = 88 - `Assoc 89 - [ ("alg", `String (algorithm_to_string h.alg)); ("typ", `String h.typ) ] 87 + type json = Simdjsont.Json.t 90 88 91 - let header_of_json (json : Yojson.Safe.t) : (header, error) result = 89 + let header_to_json (h : header) : json = 90 + Simdjsont.Json.Object 91 + [ 92 + ("alg", Simdjsont.Json.String (algorithm_to_string h.alg)); 93 + ("typ", Simdjsont.Json.String h.typ); 94 + ] 95 + 96 + let header_of_json (json : json) : (header, error) result = 92 97 match json with 93 - | `Assoc pairs -> ( 98 + | Simdjsont.Json.Object pairs -> ( 94 99 let get_string key = 95 100 match List.assoc_opt key pairs with 96 - | Some (`String s) -> Some s 101 + | Some (Simdjsont.Json.String s) -> Some s 97 102 | _ -> None 98 103 in 99 104 match get_string "alg" with ··· 107 112 | Some typ -> Ok { alg; typ }))) 108 113 | _ -> Error (`Invalid_json "header must be an object") 109 114 110 - let claims_to_json (c : claims) : Yojson.Safe.t = 115 + let claims_to_json (c : claims) : json = 111 116 let fields = 112 117 [ 113 - ("iss", `String c.iss); 114 - ("aud", `String c.aud); 115 - ("exp", `Int (Int64.to_int c.exp)); 116 - ("iat", `Int (Int64.to_int c.iat)); 118 + ("iss", Simdjsont.Json.String c.iss); 119 + ("aud", Simdjsont.Json.String c.aud); 120 + ("exp", Simdjsont.Json.Int c.exp); 121 + ("iat", Simdjsont.Json.Int c.iat); 117 122 ] 118 123 in 119 124 let add_opt key = function 120 125 | None -> Fun.id 121 - | Some v -> fun acc -> (key, `String v) :: acc 126 + | Some v -> fun acc -> (key, Simdjsont.Json.String v) :: acc 122 127 in 123 128 let fields = 124 129 fields |> add_opt "sub" c.sub |> add_opt "jti" c.jti |> add_opt "lxm" c.lxm 125 130 |> add_opt "nonce" c.nonce |> add_opt "scope" c.scope 126 131 in 127 - `Assoc fields 132 + Simdjsont.Json.Object fields 128 133 129 - let claims_of_json (json : Yojson.Safe.t) : (claims, error) result = 134 + let claims_of_json (json : json) : (claims, error) result = 130 135 match json with 131 - | `Assoc pairs -> ( 136 + | Simdjsont.Json.Object pairs -> ( 132 137 let get_string key = 133 138 match List.assoc_opt key pairs with 134 - | Some (`String s) -> Some s 139 + | Some (Simdjsont.Json.String s) -> Some s 135 140 | _ -> None 136 141 in 137 - let get_int key = 142 + let get_int64 key = 138 143 match List.assoc_opt key pairs with 139 - | Some (`Int i) -> Some (Int64.of_int i) 140 - | Some (`Intlit s) -> ( try Some (Int64.of_string s) with _ -> None) 144 + | Some (Simdjsont.Json.Int i) -> Some i 141 145 | _ -> None 142 146 in 143 147 match 144 - (get_string "iss", get_string "aud", get_int "exp", get_int "iat") 148 + (get_string "iss", get_string "aud", get_int64 "exp", get_int64 "iat") 145 149 with 146 150 | Some iss, Some aud, Some exp, Some iat -> 147 151 Ok ··· 195 199 let create ~(key : signing_key) ~(typ : string) ~(claims : claims) : t = 196 200 let alg = match key with P256_key _ -> ES256 | K256_key _ -> ES256K in 197 201 let header = { alg; typ } in 198 - let header_json = Yojson.Safe.to_string (header_to_json header) in 199 - let claims_json = Yojson.Safe.to_string (claims_to_json claims) in 202 + let header_json = 203 + Simdjsont.encode Simdjsont.Codec.value (header_to_json header) 204 + in 205 + let claims_json = 206 + Simdjsont.encode Simdjsont.Codec.value (claims_to_json claims) 207 + in 200 208 let header_b64 = base64url_encode header_json in 201 209 let claims_b64 = base64url_encode claims_json in 202 210 let signing_input = header_b64 ^ "." ^ claims_b64 in ··· 219 227 | Error _ -> Error `Invalid_base64 220 228 | Ok signature -> ( 221 229 let header_json = 222 - try Ok (Yojson.Safe.from_string header_str) 223 - with Yojson.Json_error msg -> Error (`Invalid_json msg) 230 + match Simdjsont.decode Simdjsont.Codec.value header_str with 231 + | Ok v -> Ok v 232 + | Error msg -> Error (`Invalid_json msg) 224 233 in 225 234 let claims_json = 226 - try Ok (Yojson.Safe.from_string claims_str) 227 - with Yojson.Json_error msg -> Error (`Invalid_json msg) 235 + match Simdjsont.decode Simdjsont.Codec.value claims_str with 236 + | Ok v -> Ok v 237 + | Error msg -> Error (`Invalid_json msg) 228 238 in 229 239 match (header_json, claims_json) with 230 240 | Error e, _ -> Error e