-
Notifications
You must be signed in to change notification settings - Fork 253
Yo.
So I have a requirement to add images to SD-JWT credentials (react with a 👍 or 👎 for images in SD-JWT)
However I want to store the image data in cheaper storage, something like S3.
Currently I grab the original claim/disclosure and drop it in S3 and modify the claim that is stored in the wallet with a pointer back to S3. I can then replace the modified claim/disclosure with the original when needed.
However credo does not return the SD-JWT with modified claims/disclosures (by design I expect/hope), for presentation requests.
How do we feel about allowing credo to return SD-JWT credentials with modified claims?
It feels like this might go against some VC philosophy let me know what you think.
The goal is to have a cheaper storage solution for large payloads.
Thanks
All reactions
Replies: 2 comments 4 replies
Hi @cl0ete, could you maybe provide some examples of what "allowing credo to return SD-JWT credentials with modified claims" looks like? I get what you're trying to do, but I don't understand returning modified claims looks like in this case.
All reactions
My bad...
When credo resolves a presentation request (resolveOpenId4VpAuthorizationRequest) part of the response if a list of credentials that can be used respond to the presentation request.
They are not returned in that list. Hope this clears things up
All reactions
I would still appreciate some more details with concrete examples. What is not returned? Do you have a specific request that works if you keep the SD-JWT normal, but doesn't work with the modified SD-JWT? How does the modified SD-JWT look like?
Since you're trying to do something very custom here, the more details the better. I don't know your setup, and what you have modified to make this work.
All reactions
Yo sorry for not coming back to you today will do my best to send those payloads tomorrow
All reactions
So I get credential from offer:
{
"_tags": {},
"metadata": {},
"multiInstanceState": "SingleInstanceUnused",
"id": "d82b1f97-f9ec-4a48-b1db-94688699d53a",
"createdAt": "2026年01月28日T05:42:54.474Z",
"credentialInstances": [
{
"compactSdJwtVc": "eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSIsImtpZCI6IiN6Nk1raWFraFhmMndyZFZDQVdVdHNqOG02bWViR21ueHFmSzFlRWFIVm01bVdhR1QifQ.eyJuYmYiOjE3Njk1NTg0MDAsImV4cCI6MTkyNzMyNDgwMCwidmN0IjoiaHR0cHM6Ly9kaWR4LmNvLnphL3ZjdC9hdmlkdXJhdHVjL3Bvc3RtYW4tMTUzNGNjYzctYTE1NS00NDZkLTlhNzYtYWQ2YjVhYjM2NGYxIiwic3RhdHVzIjp7InN0YXR1c19saXN0Ijp7ImlkeCI6MCwidXJpIjoiaHR0cHM6Ly9taW5pby1hcGkucGFyYWR5bS5kZXYuZGlkeHRlY2guY29tL3BhcmFkeW0tcHVibGljLW1ldGFkYXRhLzJmM2ZiZThkLTBhMjEtNDc3My1hNTJhLTcyZWFhODllMTJlMS9zdGF0dXMtbGlzdHMvNWFjOWQ2OTYtMGVkYi00OTA4LWI4MWEtOTU1M2ZhNDFlMGEwIn19LCJjbmYiOnsia2lkIjoiZGlkOmtleTp6Nk1rcVlrVXlOaTVYUVRLOXVpRnZXc3VvQmpMVDgxMnVxakNrd0FzYmI1bjYzc2YjejZNa3FZa1V5Tmk1WFFUSzl1aUZ2V3N1b0JqTFQ4MTJ1cWpDa3dBc2JiNW42M3NmIn0sImlzcyI6ImRpZDp3ZWI6bWluaW8tYXBpLnBhcmFkeW0uZGV2LmRpZHh0ZWNoLmNvbTpwYXJhZHltLXB1YmxpYy1tZXRhZGF0YToyZjNmYmU4ZC0wYTIxLTQ3NzMtYTUyYS03MmVhYTg5ZTEyZTEiLCJpYXQiOjE3Njk1Nzg5NzMsIl9zZCI6WyIzQUpZTHAwOVNVdGFlRF9HR042d2NLanp1V05wTGtDd0VzRl9ma0JCanJBIiwiN1BlQzBDYmJHVjZHT3J2QkxDOUZNcjg0Uk41MXhKMTc2UnYzS0hoMzZlbyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.UeRgVKcgb8UKb3X-9dx-YVGUPsQ_J2ELOT_QN5e7jL7jJD31p2-w51ZImy-Svb5jDn9_OCLE8C3gyJZrJJjFAQ~WyIxMDAyOTY1MjA1ODg4MTI1NDA2NjY0NzEiLCJpbWFnZSIsImRhdGE6aW1hZ2UvZ2lmO2Jhc2U2NCxSMGxHT0RkaE1BQXdBUEFBQUFBQUFQLy8veXdBQUFBQU1BQXdBQUFDOEl5UHFjdnQzd0NjRGtpTGM3QzBxd3lHSGhTV3BqUXU1eXFtQ1lzYXB5dXZVVWx2T05tT1p0ZnpnRnpCeVRCMTBRZ3hPUjBUcUJRZWpoUk56T2ZrVkorNVlpVXFyWEY1WTVsS2gvRGV1TmNQNXlMV0dzRWJ0TGlPU3BhL1RQZzdKcEpIeHllbmR6V1RCZlgwY3hPbktQamdCemk0ZGlpbldHZGtGOGtqZGZueWNRWlhaZVlHZWptSmxaZUdsOWkyaWNWcWFOVmFpbFQ2RjVpSjkwbTZtdnVUUzRPSzA1TTB2RGswUTRYVXR3dktPenJjZDNpcTl1aXNGODFNMU9JY1I3bEVld3djTHA3dHVOTmtNM3VObmEzRjJKUUZvOTdWcml5L1hsNC9mMWNmNVZXelh5eW03UEhoaHg0ZGJnWUtBQUE3Il0~WyI1MTg0OTE2NDE0NTc4OTkyNzQxMjQwOTYiLCJsYXN0TmFtZSIsIlBvc3RtYW4iXQ~"
}
]
}I find the claim store in s3. Then decode it:
["100296520588812540666471","image","data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"]
I replace value with key for s3 and re-encode the modified list and store this in the wallet.
{
"_tags": {},
"metadata": {},
"multiInstanceState": "SingleInstanceUnused",
"id": "d82b1f97-f9ec-4a48-b1db-94688699d53a",
"createdAt": "2026年01月28日T05:42:54.474Z",
"credentialInstances": [
{
"compactSdJwtVc": "eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSIsImtpZCI6IiN6Nk1raWFraFhmMndyZFZDQVdVdHNqOG02bWViR21ueHFmSzFlRWFIVm01bVdhR1QifQ.eyJuYmYiOjE3Njk1NTg0MDAsImV4cCI6MTkyNzMyNDgwMCwidmN0IjoiaHR0cHM6Ly9kaWR4LmNvLnphL3ZjdC9hdmlkdXJhdHVjL3Bvc3RtYW4tMTUzNGNjYzctYTE1NS00NDZkLTlhNzYtYWQ2YjVhYjM2NGYxIiwic3RhdHVzIjp7InN0YXR1c19saXN0Ijp7ImlkeCI6MCwidXJpIjoiaHR0cHM6Ly9taW5pby1hcGkucGFyYWR5bS5kZXYuZGlkeHRlY2guY29tL3BhcmFkeW0tcHVibGljLW1ldGFkYXRhLzJmM2ZiZThkLTBhMjEtNDc3My1hNTJhLTcyZWFhODllMTJlMS9zdGF0dXMtbGlzdHMvNWFjOWQ2OTYtMGVkYi00OTA4LWI4MWEtOTU1M2ZhNDFlMGEwIn19LCJjbmYiOnsia2lkIjoiZGlkOmtleTp6Nk1rcVlrVXlOaTVYUVRLOXVpRnZXc3VvQmpMVDgxMnVxakNrd0FzYmI1bjYzc2YjejZNa3FZa1V5Tmk1WFFUSzl1aUZ2V3N1b0JqTFQ4MTJ1cWpDa3dBc2JiNW42M3NmIn0sImlzcyI6ImRpZDp3ZWI6bWluaW8tYXBpLnBhcmFkeW0uZGV2LmRpZHh0ZWNoLmNvbTpwYXJhZHltLXB1YmxpYy1tZXRhZGF0YToyZjNmYmU4ZC0wYTIxLTQ3NzMtYTUyYS03MmVhYTg5ZTEyZTEiLCJpYXQiOjE3Njk1Nzg5NzMsIl9zZCI6WyIzQUpZTHAwOVNVdGFlRF9HR042d2NLanp1V05wTGtDd0VzRl9ma0JCanJBIiwiN1BlQzBDYmJHVjZHT3J2QkxDOUZNcjg0Uk41MXhKMTc2UnYzS0hoMzZlbyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.UeRgVKcgb8UKb3X-9dx-YVGUPsQ_J2ELOT_QN5e7jL7jJD31p2-w51ZImy-Svb5jDn9_OCLE8C3gyJZrJJjFAQ~WyIxMDAyOTY1MjA1ODg4MTI1NDA2NjY0NzEiLCJpbWFnZSIsInRlc3QtY2xhaW0udHh0Il0~WyI1MTg0OTE2NDE0NTc4OTkyNzQxMjQwOTYiLCJsYXN0TmFtZSIsIlBvc3RtYW4iXQ~"
}
]
}At this point the hash of the modified claim does not match the value in the _sd list in the JWT.
This is the failure point bc from here it looks like the SD-JWT package used in credo and in the pex lib (used incredo) does not select the credential for presentations bc the hash does not match the hashes in the JWT.
{
"authorizationRequestPayload": {
"response_type": "vp_token",
"client_id": "did:web:minio-api.paradym.dev.didxtech.com:paradym-public-metadata:2f3fbe8d-0a21-4773-a52a-72eaa89e12e1",
"response_uri": "https://agent.paradym.dev.didxtech.com/oid4vp/0df4fafb-b814-47f4-8ace-39259d43799f/authorize?session=e571cb24-4439-45b3-a6c7-1a89d40a091f",
"response_mode": "direct_post",
"nonce": "764830043163691071860414",
"presentation_definition": {
"id": "1f21087c-ebf3-4136-9b1e-655dbcf11509",
"name": "Basic Identity Request",
"purpose": "Standard request for basic identity information",
"input_descriptors": [
{
"id": "c0edbd19-fbb6-4b0b-a4db-3a40d68e0b30",
"name": "Basic Identity Credential",
"purpose": "A credential containing basic identity information",
"format": {
"vc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256"
]
}
},
"constraints": {
"limit_disclosure": "preferred",
"fields": [
{
"path": [
"$.vct"
],
"name": "vct",
"filter": {
"type": "string",
"const": "https://didx.co.za/vct/aviduratuc/postman-1534ccc7-a155-446d-9a76-ad6b5ab364f1"
}
},
{
"path": [
"$.image"
],
"name": "image",
"filter": {
"type": "string"
}
},
{
"path": [
"$.lastName"
],
"name": "lastName",
"filter": {
"type": "string"
}
}
]
}
}
]
},
"client_metadata": {
"vp_formats": {
"mso_mdoc": {
"alg": [
"EdDSA",
"ES256",
"ES384"
]
},
"jwt_vc": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vc_json": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vp_json": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vp": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"ldp_vc": {
"proof_type": [
"Ed25519Signature2018",
"Ed25519Signature2020"
]
},
"ldp_vp": {
"proof_type": [
"Ed25519Signature2018",
"Ed25519Signature2020"
]
},
"vc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"dc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
}
},
"client_name": "aviduratuc-me-creds",
"response_types_supported": [
"vp_token"
]
},
"state": "1157158014397824688856616",
"client_id_scheme": "did",
"aud": "https://agent.paradym.dev.didxtech.com/oid4vp/0df4fafb-b814-47f4-8ace-39259d43799f/authorization-requests/e571cb24-4439-45b3-a6c7-1a89d40a091f",
"iat": 1769579712,
"exp": 1769580012
},
"signedAuthorizationRequest": {
"signer": {
"method": "did",
"alg": "EdDSA",
"didUrl": "did:web:minio-api.paradym.dev.didxtech.com:paradym-public-metadata:2f3fbe8d-0a21-4773-a52a-72eaa89e12e1#z6MkiakhXf2wrdVCAWUtsj8m6mebGmnxqfK1eEaHVm5mWaGT",
"publicJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "PVo2cXpjDYHGJEOUGSmo2nSGL3wvbOvRYGy70Fc9f-w"
}
},
"payload": {
"aud": "https://agent.paradym.dev.didxtech.com/oid4vp/0df4fafb-b814-47f4-8ace-39259d43799f/authorization-requests/e571cb24-4439-45b3-a6c7-1a89d40a091f",
"iat": 1769579712,
"exp": 1769580012,
"nonce": "764830043163691071860414",
"client_id": "did:web:minio-api.paradym.dev.didxtech.com:paradym-public-metadata:2f3fbe8d-0a21-4773-a52a-72eaa89e12e1",
"response_type": "vp_token",
"response_uri": "https://agent.paradym.dev.didxtech.com/oid4vp/0df4fafb-b814-47f4-8ace-39259d43799f/authorize?session=e571cb24-4439-45b3-a6c7-1a89d40a091f",
"response_mode": "direct_post",
"presentation_definition": {
"id": "1f21087c-ebf3-4136-9b1e-655dbcf11509",
"name": "Basic Identity Request",
"purpose": "Standard request for basic identity information",
"input_descriptors": [
{
"id": "c0edbd19-fbb6-4b0b-a4db-3a40d68e0b30",
"name": "Basic Identity Credential",
"purpose": "A credential containing basic identity information",
"format": {
"vc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256"
]
}
},
"constraints": {
"limit_disclosure": "preferred",
"fields": [
{
"path": [
"$.vct"
],
"name": "vct",
"filter": {
"type": "string",
"const": "https://didx.co.za/vct/aviduratuc/postman-1534ccc7-a155-446d-9a76-ad6b5ab364f1"
}
},
{
"path": [
"$.image"
],
"name": "image",
"filter": {
"type": "string"
}
},
{
"path": [
"$.lastName"
],
"name": "lastName",
"filter": {
"type": "string"
}
}
]
}
}
]
},
"client_metadata": {
"vp_formats": {
"mso_mdoc": {
"alg": [
"EdDSA",
"ES256",
"ES384"
]
},
"jwt_vc": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vc_json": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vp_json": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"jwt_vp": {
"alg": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"ldp_vc": {
"proof_type": [
"Ed25519Signature2018",
"Ed25519Signature2020"
]
},
"ldp_vp": {
"proof_type": [
"Ed25519Signature2018",
"Ed25519Signature2020"
]
},
"vc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
},
"dc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256",
"ES384",
"ES256K"
]
}
},
"client_name": "aviduratuc-me-creds",
"response_types_supported": [
"vp_token"
]
},
"state": "1157158014397824688856616",
"client_id_scheme": "did"
},
"header": {
"alg": "EdDSA",
"typ": "oauth-authz-req+jwt",
"kid": "did:web:minio-api.paradym.dev.didxtech.com:paradym-public-metadata:2f3fbe8d-0a21-4773-a52a-72eaa89e12e1#z6MkiakhXf2wrdVCAWUtsj8m6mebGmnxqfK1eEaHVm5mWaGT"
}
},
"verifier": {
"clientIdPrefix": "decentralized_identifier",
"effectiveClientId": "did:web:minio-api.paradym.dev.didxtech.com:paradym-public-metadata:2f3fbe8d-0a21-4773-a52a-72eaa89e12e1"
},
"presentationExchange": {
"definition": {
"id": "1f21087c-ebf3-4136-9b1e-655dbcf11509",
"name": "Basic Identity Request",
"purpose": "Standard request for basic identity information",
"input_descriptors": [
{
"id": "c0edbd19-fbb6-4b0b-a4db-3a40d68e0b30",
"name": "Basic Identity Credential",
"purpose": "A credential containing basic identity information",
"format": {
"vc+sd-jwt": {
"kb-jwt_alg_values": [
"EdDSA",
"ES256"
],
"sd-jwt_alg_values": [
"EdDSA",
"ES256"
]
}
},
"constraints": {
"limit_disclosure": "preferred",
"fields": [
{
"path": [
"$.vct"
],
"name": "vct",
"filter": {
"type": "string",
"const": "https://didx.co.za/vct/aviduratuc/postman-1534ccc7-a155-446d-9a76-ad6b5ab364f1"
}
},
{
"path": [
"$.image"
],
"name": "image",
"filter": {
"type": "string"
}
},
{
"path": [
"$.lastName"
],
"name": "lastName",
"filter": {
"type": "string"
}
}
]
}
}
]
},
"credentialsForRequest": {
"requirements": [
{
"rule": "pick",
"needsCount": 1,
"submissionEntry": [
{
"inputDescriptorId": "c0edbd19-fbb6-4b0b-a4db-3a40d68e0b30",
"name": "Basic Identity Credential",
"purpose": "A credential containing basic identity information",
"verifiableCredentials": []
}
],
"isRequirementSatisfied": false
}
],
"areRequirementsSatisfied": false,
"name": "Basic Identity Request",
"purpose": "Standard request for basic identity information"
}
}
}As we see above the verifiableCredentials list is empty.
If I don't request the image attribute then it works.
All reactions
Just wanna restate that the goal here is for cheaper storage for large payloads in credentials.
I am open to any other ideas 👀
We are hosting the wallets for end-users i.e. custodial wallets.
We are looking for a solution that:
- Reduces storage costs - Large image data in credentials is expensive when stored directly in wallet storage, especially at scale with many users
- Maintains credential integrity - The credential should still be valid and verifiable
- Works within Credo's architecture - Ideally without major modifications to core credential handling