{"record":{"id":"e4c37d5e1a16451c","repo":"signalapp/Signal-Server","slug":"400-bad-request-invalid-profilekeycommitment-base","errorCode":null,"errorMessage":"400 Bad Request (invalid ProfileKeyCommitment base64)","messagePattern":"400 Bad Request \\(invalid ProfileKeyCommitment base64\\)","errorType":"http","errorClass":"IOException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCommitmentAdapter.java","lineNumber":35,"sourceCode":"import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;\n\npublic class ProfileKeyCommitmentAdapter {\n\n  public static class Serializing extends JsonSerializer<ProfileKeyCommitment> {\n    @Override\n    public void serialize(ProfileKeyCommitment value, JsonGenerator gen, SerializerProvider serializers) throws IOException {\n      gen.writeString(Base64.getEncoder().encodeToString(value.serialize()));\n    }\n  }\n\n  public static class Deserializing extends JsonDeserializer<ProfileKeyCommitment> {\n\n    @Override\n    public ProfileKeyCommitment deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\n      try {\n        return new ProfileKeyCommitment(Base64.getDecoder().decode(p.getValueAsString()));\n      } catch (InvalidInputException e) {\n        throw new IOException(e);\n      }\n    }\n  }\n}\n\n","sourceCodeStart":17,"sourceCodeEnd":41,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCommitmentAdapter.java#L17-L41","documentation":"Custom Jackson deserializer for the ProfileKeyCommitment entity. It base64-decodes the incoming JSON string and constructs a ProfileKeyCommitment (which validates the decoded bytes via libsignal's ProfileKeyCommitment). Any decode or InvalidInputException (wrong length, malformed base64) is rethrown as IOException, which Jackson surfaces as a deserialization failure and the server responds 400 with 'invalid ProfileKeyCommitment base64'.","triggerScenarios":"PUT /v1/profile with a profile key commitment field that is empty, not base64 at all, base64 with invalid characters, or decodes to a byte length other than the 32-byte-per-profile-key commitment expected by Signal's ProfileKey format.","commonSituations":"Client implementations that hex-encode instead of base64-encode, sending an empty commitment before the key is generated, truncating/corrupting the commitment in transport, or old client versions that computed commitments differently.","solutions":["Base64-encode the 32-byte profile key and derive the commitment with ProfileKey.getCommitment(ContentHint.NONE) client-side before uploading","Verify the commitment is exactly the expected length (32 bytes) before sending","Ensure standard (not URL-safe or hex) base64 encoding is used","Check that the field is non-null and non-empty in the PUT /v1/profile request body"],"exampleFix":"// before\nString commitment = Hex.toStringCondensed(commitmentBytes);\n// after\nString commitment = Base64.getEncoder().encodeToString(commitmentBytes);","handlingStrategy":"validation","validationCode":"if (commitmentBase64 == null || commitmentBase64.isEmpty()) throw new IllegalArgumentException(\"commitment missing\");\nbyte[] decoded = Base64.getDecoder().decode(commitmentBase64);\nif (decoded.length != 32) throw new IllegalArgumentException(\"commitment must be 32 bytes, got \" + decoded.length);","typeGuard":"static boolean isValidCommitment(String s) {\n  if (s == null || s.isEmpty()) return false;\n  try { return Base64.getDecoder().decode(s).length == 32; } catch (IllegalArgumentException e) { return false; }\n}","tryCatchPattern":"try {\n  api.setProfile(commitmentBase64);\n} catch (ServerErrorException e) {\n  if (e.getMessage().contains(\"invalid ProfileKeyCommitment base64\")) {\n    // re-encode/re-derive the commitment from the profile key and retry once\n  }\n}","preventionTips":["Always derive the commitment from ProfileKey.getCommitment() and base64-encode immediately before upload","Never hex-encode commitment fields","Unit-test serialization of the profile entity against the server schema"],"tags":["base64","jackson","deserialization","profile-key"],"backgroundTag":"invalid-argument-format","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}