session.go 17.7 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
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
33
	UnsatisfiableRequest(action irma.Action, ServerName string, missing irma.AttributeDisjunctionList)
34
35
36
37
38
39
	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
	return true
}

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

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
func (session *session) checkAndUpateConfiguration(client *Client) bool {
	var err error
	for id := range session.irmaSession.Identifiers().SchemeManagers {
		manager, contains := client.Configuration.SchemeManagers[id]
		if !contains {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorUnknownSchemeManager,
				Info:      id.String(),
			})
			return false
		}
		if !manager.Valid {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorInvalidSchemeManager,
				Info:      string(manager.Status),
			})
			return false
		}
	}

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

	// 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 false
	}
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
206
// NewManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
Koen van Ingen's avatar
Koen van Ingen committed
207
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
211
212
213
214
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

215
216
217
218
219
220
	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
221
222
223
224
	}

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

225
	if !session.checkAndUpateConfiguration(client) {
226
227
228
		return
	}

229
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
230
	if len(missing) > 0 {
231
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
232
233
234
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
235
	session.irmaSession.SetCandidates(candidates)
236
237
238
239

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
240
		session.irmaSession.SetDisclosureChoice(choice)
Koen van Ingen's avatar
Koen van Ingen committed
241
		go session.do(proceed)
242
243
	})
	session.Handler.RequestSignaturePermission(
Koen van Ingen's avatar
Koen van Ingen committed
244
		*session.irmaSession.(*irma.SignatureRequest), "E-mail request", callback)
245
246
}

247
// NewSession creates and starts a new interactive IRMA session
248
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
249
	session := &session{
250
		ServerURL: qr.URL,
251
		transport: irma.NewHTTPTransport(qr.URL),
252
253
254
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
255
	}
256
257
258
259
260
261

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

262
263
	version, err := calcVersion(qr)
	if err != nil {
264
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
265
		return nil
266
	}
267
	session.Version = irma.Version(version)
268
269
270

	// Check if the action is one of the supported types
	switch session.Action {
271
272
273
274
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
275
276
		fallthrough
	default:
277
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
278
		return nil
279
280
281
282
283
284
285
286
	}

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

	go session.start()

287
	return session
288
289
}

290
291
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
292
func (session *session) start() {
Koen van Ingen's avatar
Koen van Ingen committed
293
	defer session.panicFailure()
294

295
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
296

Sietse Ringers's avatar
Sietse Ringers committed
297
	// Get the first IRMA protocol message and parse it
298
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
299
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
300
	if Err != nil {
301
		session.fail(Err.(*irma.SessionError))
302
303
304
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
305
	var err error
306
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
307
	if err != nil {
308
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
309
310
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
311
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
312
313
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
314
315
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
316
		// Store which public keys the server will use
317
		for _, credreq := range ir.Credentials {
318
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
319
320
321
		}
	}

322
	if !session.checkAndUpateConfiguration(session.client) {
323
324
325
		return
	}

326
327
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
328
		for _, credreq := range ir.Credentials {
329
			info, err := credreq.Info(session.client.Configuration)
330
			if err != nil {
331
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
332
333
334
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
335
336
337
		}
	}

338
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
339
	if len(missing) > 0 {
340
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
341
		// TODO: session.transport.Delete() on dialog cancel
342
343
		return
	}
344
	session.irmaSession.SetCandidates(candidates)
345

Sietse Ringers's avatar
Sietse Ringers committed
346
	// Ask for permission to execute the session
347
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
348
349
350
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
351
	})
352
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
353
	switch session.Action {
354
	case irma.ActionDisclosing:
355
		session.Handler.RequestVerificationPermission(
356
357
			*session.irmaSession.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
	case irma.ActionSigning:
358
		session.Handler.RequestSignaturePermission(
359
360
			*session.irmaSession.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
	case irma.ActionIssuing:
361
		session.Handler.RequestIssuancePermission(
362
			*session.irmaSession.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
363
364
365
366
367
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

368
func (session *session) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
369
	defer session.panicFailure()
370

371
	if !proceed {
372
		session.cancel()
373
374
		return
	}
375
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
376

377
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
378
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
379
		if err != nil {
380
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
381
382
383
384
			return
		}
		session.sendResponse(message)
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
385
		builders, err := session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
386
		if err != nil {
387
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
388
		}
389
390
391
392
393
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
394
			session.client.Configuration,
395
			session.client.keyshareServers,
396
			session.client.state,
397
		)
398
	}
Sietse Ringers's avatar
Sietse Ringers committed
399
}
400

401
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
402
403
404
	session.sendResponse(message)
}

405
func (session *session) KeyshareCancelled() {
406
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
407
408
}

409
func (session *session) KeyshareBlocked(duration int) {
410
	session.fail(&irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
411
412
}

413
func (session *session) KeyshareError(err error) {
414
415
416
417
418
419
420
421
	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
422
423
}

Koen van Ingen's avatar
Koen van Ingen committed
424
func (session *session) KeysharePin() {
425
426
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
427

Koen van Ingen's avatar
Koen van Ingen committed
428
func (session *session) KeysharePinOK() {
429
430
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
431

Sietse Ringers's avatar
Sietse Ringers committed
432
433
type disclosureResponse string

434
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
435
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
436
	var err error
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
	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
465
		}
466
467
468
469
	} 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
470
471
			return
		}
472
473
	}

474
	_ = session.client.addLogEntry(log) // TODO err
475
	if !session.downloaded.Empty() {
476
		session.client.handler.UpdateConfiguration(session.downloaded)
477
	}
478
	if session.Action == irma.ActionIssuing {
479
		session.client.handler.UpdateAttributes()
480
	}
481
	session.done = true
482
483
484
	session.Handler.Success(session.Action, string(messageJson))
}

485
func (session *session) managerSession() {
486
	defer func() {
487
		if e := recover(); e != nil {
488
			if session.Handler != nil {
489
				session.Handler.Failure(session.Action, panicToError(e))
490
			}
491
		}
492
493
494
495
496
	}()

	// 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.
497
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
498
	if err != nil {
499
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
500
501
		return
	}
502

503
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
504
		if !proceed {
505
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
506
507
			return
		}
508
		if err := session.client.Configuration.AddSchemeManager(manager); err != nil {
509
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
510
511
			return
		}
512
513

		// Update state and inform user of success
514
		if manager.Distributed() {
515
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
516
		}
517
		session.client.handler.UpdateConfiguration(
518
519
520
521
			&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
522
523
			},
		)
524
		session.Handler.Success(session.Action, "")
525
526
527
	})
	return
}
528
529
530

// Session lifetime functions

531
532
533
534
535
536
537
538
539
540
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
541
	}
542
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
543
544
545
}

// Idempotently send DELETE to remote server, returning whether or not we did something
546
func (session *session) delete() bool {
547
	if !session.done {
548
549
550
		if session.IsInteractive() {
			session.transport.Delete()
		}
551
		session.done = true
552
553
554
555
556
		return true
	}
	return false
}

557
func (session *session) fail(err *irma.SessionError) {
558
559
560
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
561
			session.client.handler.UpdateConfiguration(session.downloaded)
562
563
564
565
566
		}
		session.Handler.Failure(session.Action, err)
	}
}

567
func (session *session) cancel() {
568
569
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
570
			session.client.handler.UpdateConfiguration(session.downloaded)
571
572
573
574
575
		}
		session.Handler.Cancelled(session.Action)
	}
}

576
func (session *session) Dismiss() {
577
578
	session.cancel()
}
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594

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
}