Skip to content

feat: batch issuance#346

Open
smncd wants to merge 20 commits into
SUNET:mainfrom
sirosfoundation:feat/wip-batch-issuance
Open

feat: batch issuance#346
smncd wants to merge 20 commits into
SUNET:mainfrom
sirosfoundation:feat/wip-batch-issuance

Conversation

@smncd
Copy link
Copy Markdown
Contributor

@smncd smncd commented Apr 10, 2026

A quick and messy implementation of batch issuance of credentials. I did this for a unrelated demo that needed batch-issued creds, putting it here in case it turns out it's useful. Probably needs work.

Comment thread pkg/openid4vci/proof_attestation.go Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial support for batch credential issuance by allowing multiple proofs/keys to be processed and returning multiple credentials in a single OpenID4VCI credential response.

Changes:

  • Extend proof parsing to extract multiple JWKs (including multiple attested_keys from key attestations).
  • Update issuer credential handler to issue one credential per extracted JWK and return a credentials[] array.
  • Add unit tests for batch JWK extraction/counting and basic batch request structuring.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pkg/openid4vci/proof_attestation.go Adds ExtractAllJWKs() to return all attested keys; refactors ExtractJWK() to reuse it.
pkg/openid4vci/credential.go Adds Proofs.ExtractAllJWKs() and Proofs.Count() to support batch flows.
pkg/openid4vci/credential_test.go Adds tests for ExtractAllJWKs() and Proofs.Count().
internal/apigw/apiv1/handlers_issuer.go Implements batch issuance loop and batch-size validation; refactors issue helpers to return credential strings.
internal/apigw/apiv1/handlers_issuer_test.go Adds tests around batch proof extraction and batch request structure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/apigw/apiv1/handlers_issuer.go Outdated
Comment thread internal/apigw/apiv1/handlers_issuer.go
Comment thread pkg/openid4vci/credential.go Outdated
Comment on lines +208 to +212
t.Run("single JWT proof", func(t *testing.T) {
proofs := &Proofs{JWT: []ProofJWTToken{mockProofJWT}}
jwks, err := proofs.ExtractAllJWKs()
assert.NoError(t, err)
assert.Len(t, jwks, 1)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestExtractAllJWKs only exercises the JWT branch. Since ExtractAllJWKs() now has separate paths for DIVP and Attestation, add subtests that cover those branches (including an attestation with multiple attested_keys) to prevent regressions in batch issuance behavior.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in acf346c. Very AI'd, so would appreciate some eyes on it @masv3971

Comment thread internal/apigw/apiv1/handlers_issuer_test.go
smncd added 3 commits April 13, 2026 12:54
…o get the batch size.

- `Proofs.Count()` would need to extract all JWKs to get the correct count for attestations, and at that point it makes more sense to not use it and simply check the length of the already extracted JWKs
@smncd smncd marked this pull request as ready for review April 13, 2026 11:20
@smncd smncd requested a review from Copilot April 13, 2026 11:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/apigw/apiv1/handlers_issuer.go Outdated
Comment thread internal/apigw/apiv1/handlers_issuer.go Outdated
Comment thread internal/apigw/apiv1/handlers_issuer.go Outdated
Comment thread pkg/openid4vci/credential.go Outdated
Comment thread pkg/openid4vci/proof_attestation.go
@smncd smncd force-pushed the feat/wip-batch-issuance branch from f2bc06d to f761ded Compare April 13, 2026 11:43
Comment thread pkg/openid4vci/credential.go Outdated
Comment thread pkg/openid4vci/credential.go Outdated
var jwks []*apiv1_issuer.Jwk
if req.Proof != nil {
jwk, err = req.Proof.ExtractJWK()
jwk, err := req.Proof.ExtractJWK()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think proof is an compromise from when wallet did not support proofs, not sure however. If wallet supports proofs then this could be removed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is Proofs is the way forward in the spec.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's correct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we skip the support for Proof now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove it I think

Comment thread internal/apigw/apiv1/handlers_issuer_test.go
Comment thread pkg/openid4vci/credential_test.go
Comment thread pkg/openid4vci/credential.go Outdated
@smncd smncd requested a review from masv3971 April 13, 2026 17:43
@masv3971 masv3971 requested a review from Copilot April 14, 2026 08:19
var jwks []*apiv1_issuer.Jwk
if req.Proof != nil {
jwk, err = req.Proof.ExtractJWK()
jwk, err := req.Proof.ExtractJWK()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's correct.

}

// Save credential subject information to the registry for status management.
func (c *Client) saveCredentialSubjects(ctx context.Context, document *model.CompleteDocument, entries []statusEntry) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, exit the loop if a save fails and return the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's a major fault if a credential not getting saved in registry. If that happens the status of that credential won't be able to change its status.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, sounds good. Will push a fix asap.

Comment thread pkg/openid4vci/credential.go Outdated
}

// AssertSingleProofType checks that only one proof type is used per request.
func (p *Proofs) AssertSingleProofType() (string, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, use validator v10, pkg/helpers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean like this? 36a3659

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could work, I wonder if it would work in a situation where two different proof types are given. Since we want to make sure only one is present.
Since the docs for this says:

The field under validation must be present and not empty only when any of the other specified fields are not present

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, from manual: Usage: required_without=Field1 Field2

means that the fieldd using the obove struct tag munst not be present if any of Field1 or Field2 are not nil.

if that work we dont need a custom validaton function and that's a goal in it self :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try it :)

Comment thread pkg/openid4vci/credential_test.go Outdated
@sonarqubecloud
Copy link
Copy Markdown

@smncd smncd changed the title feat(wip): batch issuance feat: batch issuance Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants