session.go 17.4 KB
Newer Older
1
package irmaclient
2
3

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
6
	"fmt"
	"sort"
7
	"strconv"
8
9
	"strings"

10
11
	"math/big"

12
	"github.com/go-errors/errors"
13
	"github.com/mhe/gabi"
14
	"github.com/privacybydesign/irmago"
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
18
19
// This file contains the client side of the IRMA protocol, as well as the Handler interface
// which is used to communicate session info with the user.

Sietse Ringers's avatar
Sietse Ringers committed
20
21
// PermissionHandler is a callback for providing permission for an IRMA session
// and specifying the attributes to be disclosed.
22
type PermissionHandler func(proceed bool, choice *irma.DisclosureChoice)
23

Sietse Ringers's avatar
Sietse Ringers committed
24
// PinHandler is used to provide the user's PIN code.
25
26
type PinHandler func(proceed bool, pin string)

27
28
// A Handler contains callbacks for communication to the user.
type Handler interface {
29
	StatusUpdate(action irma.Action, status irma.Status)
30
	Success(action irma.Action, result string)
31
32
33
34
35
36
37
38
39
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
	UnsatisfiableRequest(action irma.Action, missing irma.AttributeDisjunctionList)
	MissingKeyshareEnrollment(manager irma.SchemeManagerIdentifier)

	RequestIssuancePermission(request irma.IssuanceRequest, ServerName string, callback PermissionHandler)
	RequestVerificationPermission(request irma.DisclosureRequest, ServerName string, callback PermissionHandler)
	RequestSignaturePermission(request irma.SignatureRequest, ServerName string, callback PermissionHandler)
	RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
40

41
	RequestPin(remainingAttempts int, callback PinHandler)
42
43
}

Sietse Ringers's avatar
Sietse Ringers committed
44
// SessionDismisser can dismiss the current IRMA session.
45
46
47
48
type SessionDismisser interface {
	Dismiss()
}

Sietse Ringers's avatar
Sietse Ringers committed
49
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
50
51
52
	Action  irma.Action
	Handler Handler
	Version irma.Version
53
54
55

	choice      *irma.DisclosureChoice
	client      *Client
Koen van Ingen's avatar
Koen van Ingen committed
56
	downloaded  *irma.IrmaIdentifierSet
57
	irmaSession irma.IrmaSession
58
	done        bool
59

60
	// These are empty on manual sessions
61
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
62
63
64
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
65
66
}

67
// We implement the handler for the keyshare protocol
68
var _ keyshareSessionHandler = (*session)(nil)
69

70
71
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
72
	2: {2, 1},
73
74
}

75
func calcVersion(qr *irma.Qr) (string, error) {
76
	// Parse range supported by server
77
78
79
80
81
82
83
84
85
86
87
88
	var minmajor, minminor, maxmajor, maxminor int
	var err error
	if minmajor, err = strconv.Atoi(string(qr.ProtocolVersion[0])); err != nil {
		return "", err
	}
	if minminor, err = strconv.Atoi(string(qr.ProtocolVersion[2])); err != nil {
		return "", err
	}
	if maxmajor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[0])); err != nil {
		return "", err
	}
	if maxminor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[2])); err != nil {
89
90
91
92
93
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
94
	for k := range supportedVersions {
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > minmajor || (major == minmajor && minor >= minminor)
			underMaximum := major < maxmajor || (major == maxmajor && minor <= maxminor)
			if aboveMinimum && underMaximum {
				return fmt.Sprintf("%d.%d", major, minor), nil
			}
		}
	}
	return "", fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
}

110
111
112
113
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

Koen van Ingen's avatar
Koen van Ingen committed
114
115
116
117
118
119
120
121
122
123
124
125
func (session *session) getBuilders() (gabi.ProofBuilderList, error) {
	var builders gabi.ProofBuilderList
	var err error

	switch session.Action {
	case irma.ActionSigning:
		fallthrough
	case irma.ActionDisclosing:
		builders, err = session.client.ProofBuilders(session.choice)
	case irma.ActionIssuing:
		builders, err = session.client.IssuanceProofBuilders(session.irmaSession.(*irma.IssuanceRequest))
	}
126

Koen van Ingen's avatar
Koen van Ingen committed
127
	return builders, err
128
129
}

Koen van Ingen's avatar
Koen van Ingen committed
130
131
132
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
133

Koen van Ingen's avatar
Koen van Ingen committed
134
135
136
137
138
139
140
141
	switch session.Action {
	case irma.ActionSigning:
		message, err = session.client.Proofs(session.choice, session.irmaSession, true)
	case irma.ActionDisclosing:
		message, err = session.client.Proofs(session.choice, session.irmaSession, false)
	case irma.ActionIssuing:
		message, err = session.client.IssueCommitments(session.irmaSession.(*irma.IssuanceRequest))
	}
142

Koen van Ingen's avatar
Koen van Ingen committed
143
144
	return message, err
}
145

146
147
// checkKeyshareEnrollment checks if we are enrolled into all involved keyshare servers,
// and aborts the session if not
Koen van Ingen's avatar
Koen van Ingen committed
148
func (session *session) checkKeyshareEnrollment() bool {
149
	for id := range session.irmaSession.Identifiers().SchemeManagers {
150
151
152
		manager, ok := session.client.Configuration.SchemeManagers[id]
		if !ok {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
Koen van Ingen's avatar
Koen van Ingen committed
153
			return false
154
155
156
157
158
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.MissingKeyshareEnrollment(id)
Koen van Ingen's avatar
Koen van Ingen committed
159
			return false
160
161
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
	return true
}

func (session *session) panicFailure() {
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

func parseSigrequestJSON(sigrequestJSONString string) (*irma.SignatureRequest, error) {
	sigrequestJSON := []byte(sigrequestJSONString)
	sigrequest := &irma.SignatureRequest{}
	err := json.Unmarshal(sigrequestJSON, sigrequest)

	return sigrequest, err
}

// Start a manual session
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
	sigrequest, err := parseSigrequestJSON(sigrequestJSONString)
	if err != nil {
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

189
190
191
192
193
194
	session := &session{
		Action:      irma.ActionSigning, // TODO hardcoded for now
		Handler:     handler,
		client:      client,
		Version:     irma.Version("2"), // TODO hardcoded for now
		irmaSession: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
195
196
197
198
199
200
201
202
	}

	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)

	// Check if we are enrolled into all involved keyshare servers
	if !session.checkKeyshareEnrollment() {
		return
	}
203

204
205
206
207
208
209
	// Download missing credential types/issuers/public keys from the scheme manager
	if session.downloaded, err = session.client.Configuration.Download(session.irmaSession.Identifiers()); err != nil {
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
		return
	}

210
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
211
212
213
214
215
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
216
	session.irmaSession.SetCandidates(candidates)
217
218
219
220

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
221
		session.irmaSession.SetDisclosureChoice(choice)
Koen van Ingen's avatar
Koen van Ingen committed
222
		go session.do(proceed)
223
224
	})
	session.Handler.RequestSignaturePermission(
Koen van Ingen's avatar
Koen van Ingen committed
225
		*session.irmaSession.(*irma.SignatureRequest), "E-mail request", callback)
226
227
}

228
// NewSession creates and starts a new interactive IRMA session
229
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
230
	session := &session{
231
		ServerURL: qr.URL,
232
		transport: irma.NewHTTPTransport(qr.URL),
233
234
235
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
236
	}
237
238
239
240
241
242

	if session.Action == irma.ActionSchemeManager {
		go session.managerSession()
		return session
	}

243
244
	version, err := calcVersion(qr)
	if err != nil {
245
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
246
		return nil
247
	}
248
	session.Version = irma.Version(version)
249
250
251

	// Check if the action is one of the supported types
	switch session.Action {
252
253
254
255
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
256
257
		fallthrough
	default:
258
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
259
		return nil
260
261
262
263
264
265
266
267
	}

	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}

	go session.start()

268
	return session
269
270
}

271
272
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
273
func (session *session) start() {
Koen van Ingen's avatar
Koen van Ingen committed
274
	defer session.panicFailure()
275

276
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
277

Sietse Ringers's avatar
Sietse Ringers committed
278
	// Get the first IRMA protocol message and parse it
279
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
280
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
281
	if Err != nil {
282
		session.fail(Err.(*irma.SessionError))
283
284
285
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
286
	var err error
287
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
288
	if err != nil {
289
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
290
291
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
292
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
293
294
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
295
296
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
297
		// Store which public keys the server will use
298
		for _, credreq := range ir.Credentials {
299
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
300
301
302
		}
	}

303
	// Check if we are enrolled into all involved keyshare servers
Koen van Ingen's avatar
Koen van Ingen committed
304
305
	if !session.checkKeyshareEnrollment() {
		return
Sietse Ringers's avatar
Sietse Ringers committed
306
307
	}

Sietse Ringers's avatar
Sietse Ringers committed
308
	// Download missing credential types/issuers/public keys from the scheme manager
309
	if session.downloaded, err = session.client.Configuration.Download(session.irmaSession.Identifiers()); err != nil {
310
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
311
312
313
		return
	}

314
315
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
316
		for _, credreq := range ir.Credentials {
317
			info, err := credreq.Info(session.client.Configuration)
318
			if err != nil {
319
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
320
321
322
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
323
324
325
		}
	}

326
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
327
328
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
329
		// TODO: session.transport.Delete() on dialog cancel
330
331
		return
	}
332
	session.irmaSession.SetCandidates(candidates)
333

Sietse Ringers's avatar
Sietse Ringers committed
334
	// Ask for permission to execute the session
335
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
336
337
338
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
339
	})
340
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
341
	switch session.Action {
342
	case irma.ActionDisclosing:
343
		session.Handler.RequestVerificationPermission(
344
345
			*session.irmaSession.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
	case irma.ActionSigning:
346
		session.Handler.RequestSignaturePermission(
347
348
			*session.irmaSession.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
	case irma.ActionIssuing:
349
		session.Handler.RequestIssuancePermission(
350
			*session.irmaSession.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
351
352
353
354
355
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

356
func (session *session) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
357
	defer session.panicFailure()
358

359
	if !proceed {
360
		session.cancel()
361
362
		return
	}
363
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
364

365
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
366
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
367
		if err != nil {
368
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
369
370
371
372
			return
		}
		session.sendResponse(message)
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
373
		builders, err := session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
374
		if err != nil {
375
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
376
		}
377
378
379
380
381
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
382
			session.client.Configuration,
383
			session.client.keyshareServers,
384
			session.client.state,
385
		)
386
	}
Sietse Ringers's avatar
Sietse Ringers committed
387
}
388

389
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
390
391
392
	session.sendResponse(message)
}

393
func (session *session) KeyshareCancelled() {
394
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
395
396
}

397
func (session *session) KeyshareBlocked(duration int) {
398
	session.fail(&irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
399
400
}

401
func (session *session) KeyshareError(err error) {
402
403
404
405
406
407
408
409
	var serr *irma.SessionError
	var ok bool
	if serr, ok = err.(*irma.SessionError); !ok {
		serr = &irma.SessionError{ErrorType: irma.ErrorKeyshare, Err: err}
	} else {
		serr.ErrorType = irma.ErrorKeyshare
	}
	session.fail(serr)
Sietse Ringers's avatar
Sietse Ringers committed
410
411
}

Koen van Ingen's avatar
Koen van Ingen committed
412
func (session *session) KeysharePin() {
413
414
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
415

Koen van Ingen's avatar
Koen van Ingen committed
416
func (session *session) KeysharePinOK() {
417
418
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
419

Sietse Ringers's avatar
Sietse Ringers committed
420
421
type disclosureResponse string

422
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
423
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
424
	var err error
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
	var messageJson []byte

	if session.IsInteractive() {
		switch session.Action {
		case irma.ActionSigning:
			fallthrough
		case irma.ActionDisclosing:
			var response disclosureResponse
			if err = session.transport.Post("proofs", &response, message); err != nil {
				session.fail(err.(*irma.SessionError))
				return
			}
			if response != "VALID" {
				session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
				return
			}
			log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
		case irma.ActionIssuing:
			response := []*gabi.IssueSignatureMessage{}
			if err = session.transport.Post("commitments", &response, message); err != nil {
				session.fail(err.(*irma.SessionError))
				return
			}
			if err = session.client.ConstructCredentials(response, session.irmaSession.(*irma.IssuanceRequest)); err != nil {
				session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
				return
			}
			log, _ = session.createLogEntry(message) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
453
		}
454
455
456
457
	} else {
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
458
459
			return
		}
460
461
	}

462
	_ = session.client.addLogEntry(log) // TODO err
463
	if !session.downloaded.Empty() {
464
		session.client.handler.UpdateConfiguration(session.downloaded)
465
	}
466
	if session.Action == irma.ActionIssuing {
467
		session.client.handler.UpdateAttributes()
468
	}
469
	session.done = true
470
471
472
	session.Handler.Success(session.Action, string(messageJson))
}

473
func (session *session) managerSession() {
474
	defer func() {
475
		if e := recover(); e != nil {
476
			if session.Handler != nil {
477
				session.Handler.Failure(session.Action, panicToError(e))
478
			}
479
		}
480
481
482
483
484
	}()

	// We have to download the scheme manager description.xml here before installing it,
	// because we need to show its contents (name, description, website) to the user
	// when asking installation permission.
485
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
486
	if err != nil {
487
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
488
489
		return
	}
490

491
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
492
		if !proceed {
493
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
494
495
			return
		}
496
		if err := session.client.Configuration.AddSchemeManager(manager); err != nil {
497
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
498
499
			return
		}
500
501

		// Update state and inform user of success
502
		if manager.Distributed() {
503
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
504
		}
505
		session.client.handler.UpdateConfiguration(
506
507
508
509
			&irma.IrmaIdentifierSet{
				SchemeManagers:  map[irma.SchemeManagerIdentifier]struct{}{manager.Identifier(): {}},
				Issuers:         map[irma.IssuerIdentifier]struct{}{},
				CredentialTypes: map[irma.CredentialTypeIdentifier]struct{}{},
Sietse Ringers's avatar
Sietse Ringers committed
510
511
			},
		)
512
		session.Handler.Success(session.Action, "")
513
514
515
	})
	return
}
516
517
518

// Session lifetime functions

519
520
521
522
523
524
525
526
527
528
func panicToError(e interface{}) *irma.SessionError {
	var info string
	switch x := e.(type) {
	case string:
		info = x
	case error:
		info = x.Error()
	case fmt.Stringer:
		info = x.String()
	default: // nop
529
	}
530
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
531
532
533
}

// Idempotently send DELETE to remote server, returning whether or not we did something
534
func (session *session) delete() bool {
535
	if !session.done {
536
537
538
		if session.IsInteractive() {
			session.transport.Delete()
		}
539
		session.done = true
540
541
542
543
544
		return true
	}
	return false
}

545
func (session *session) fail(err *irma.SessionError) {
546
547
548
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
549
			session.client.handler.UpdateConfiguration(session.downloaded)
550
551
552
553
554
		}
		session.Handler.Failure(session.Action, err)
	}
}

555
func (session *session) cancel() {
556
557
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
558
			session.client.handler.UpdateConfiguration(session.downloaded)
559
560
561
562
563
		}
		session.Handler.Cancelled(session.Action)
	}
}

564
func (session *session) Dismiss() {
565
566
	session.cancel()
}
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582

type issuanceState struct {
	nonce2   *big.Int
	builders []*gabi.CredentialBuilder
}

func newIssuanceState() (*issuanceState, error) {
	nonce2, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[4096].Lstatzk)
	if err != nil {
		return nil, err
	}
	return &issuanceState{
		nonce2:   nonce2,
		builders: []*gabi.CredentialBuilder{},
	}, nil
}