revocation.go 21.5 KB
Newer Older
1
2
3
package irma

import (
4
	"database/sql/driver"
5
6
7
8
	"fmt"
	"time"

	"github.com/go-errors/errors"
9
	"github.com/hashicorp/go-multierror"
10
	"github.com/jinzhu/gorm"
11
	"github.com/privacybydesign/gabi"
12
	"github.com/privacybydesign/gabi/big"
13
	"github.com/privacybydesign/gabi/revocation"
14
	"github.com/privacybydesign/gabi/signed"
15
16
17

	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
18
19
)

20
type (
Sietse Ringers's avatar
Sietse Ringers committed
21
22
23
	// RevocationStorage stores and retrieves revocation-related data from and to a SQL database,
	// and offers a revocation API for all other irmago code, including a Revoke() method that
	// revokes an earlier issued credential.
24
25
26
27
28
29
30
31
32
	RevocationStorage struct {
		conf     *Configuration
		db       revStorage
		memdb    memRevStorage
		sqlMode  bool
		settings map[CredentialTypeIdentifier]*RevocationSetting

		Keys   RevocationKeys
		client RevocationClient
33
34
	}

Sietse Ringers's avatar
Sietse Ringers committed
35
	// RevocationClient offers an HTTP client to the revocation server endpoints.
36
37
38
39
	RevocationClient struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
40
41
	// RevocationKeys contains helper functions for retrieving revocation private and public keys
	// from an irma.Configuration instance.
42
43
44
45
	RevocationKeys struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
46
	// RevocationSetting contains revocation settings for a given credential type.
47
	RevocationSetting struct {
48
		Mode                     RevocationMode `json:"mode" mapstructure:"mode"`
49
		PostURLs                 []string       `json:"post_urls" mapstructure:"post_urls"`
50
		ServerURL                string         `json:"server_url" mapstructure:"server_url"`
51
		MaxNonrevocationDuration uint           `json:"max_nonrev_duration" mapstructure:"max_nonrev_duration"` // in seconds, min 30
52

53
54
		// set to now whenever a new update is received, or when the RA indicates
		// there are no new updates. Thus it specifies up to what time our nonrevocation
55
56
		// guarantees lasts.
		updated time.Time
57
58
	}

Sietse Ringers's avatar
Sietse Ringers committed
59
60
	// RevocationMode specifies for a given credential type what revocation operations are
	// supported, and how the associated data is stored (SQL or memory).
61
	RevocationMode string
62
)
63

64
// Structs corresponding to SQL table rows, ending in Record
65
type (
66
67
68
69
70
71
72
	// signedMessage is a signed.Message with DB (un)marshaling methods.
	signedMessage signed.Message
	// RevocationAttribute is a big.Int with DB (un)marshaling methods.
	RevocationAttribute big.Int
	// eventHash is a revocation.Hash with DB (un)marshaling methods.
	eventHash revocation.Hash

73
	AccumulatorRecord struct {
74
75
76
		CredType CredentialTypeIdentifier `gorm:"primary_key"`
		Data     signedMessage
		PKIndex  uint
77
78
	}

79
	EventRecord struct {
80
81
82
83
		Index      uint64                   `gorm:"primary_key;column:eventindex"`
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
		E          *RevocationAttribute
		ParentHash eventHash
84
85
86
87
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
88
		Key        string                   `gorm:"primary_key;column:revocationkey"`
89
90
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
		Attr       *RevocationAttribute
91
92
93
94
		Issued     int64
		ValidUntil int64
		RevokedAt  int64 // 0 if not currently revoked
	}
95
96
97
98
99
100

	// TODO
	TimeRecord struct {
		Index      uint64
		Start, End int64
	}
101
)
102

103
const (
Sietse Ringers's avatar
Sietse Ringers committed
104
105
	// RevocationModeRequestor is the default revocation mode in which only RevocationRecord instances
	// are consumed for issuance or verification. Uses an in-memory store.
106
	RevocationModeRequestor RevocationMode = ""
Sietse Ringers's avatar
Sietse Ringers committed
107
108

	// RevocationModeProxy indicates that this server
109
110
	// (1) allows fetching of revocation update messages from its database,
	// (2) relays all revocation updates it receives to the URLs configured in the containing
Sietse Ringers's avatar
Sietse Ringers committed
111
	// RevocationSetting struct.
112
	// Requires a SQL server to store and retrieve update messages from.
Sietse Ringers's avatar
Sietse Ringers committed
113
114
	RevocationModeProxy RevocationMode = "proxy"

115
	// RevocationModeServer indicates that this is a revocation server for a credential type.
Sietse Ringers's avatar
Sietse Ringers committed
116
117
118
	// IssuanceRecord instances are sent to this server, as well as revocation commands, through
	// revocation sessions or through the RevocationStorage.Revoke() method.
	// Requires a SQL server to store and retrieve all records from and requires the issuer's
119
	// private key to be accessible, in order to revoke and to sign new revocation update messages.
120
	// In addition this mode exposes the same endpoints as RevocationModeProxy.
Sietse Ringers's avatar
Sietse Ringers committed
121
122
	RevocationModeServer RevocationMode = "server"

123
	// revocationUpdateCount specifies how many revocation events are attached to session requests
Sietse Ringers's avatar
Sietse Ringers committed
124
125
	// for the client to update its revocation state.
	revocationUpdateCount = 5
126

127
128
129
	// revocationMaxAccumulatorAge is the default maximum in seconds for the 'accumulator age',
	// which we define to be the amount of time since the last confirmation from the RA that the
	// latest accumulator that we know is still the latest one: clients should prove nonrevocation
130
	// against a 'younger' accumulator.
131
	revocationMaxAccumulatorAge uint = 5 * 60
132
133
)

Sietse Ringers's avatar
Sietse Ringers committed
134
135
136
// EnableRevocation creates an initial accumulator for a given credential type. This function is the
// only way to create such an initial accumulator and it must be called before anyone can use
// revocation for this credential type. Requires the issuer private key.
137
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
138
	hasRecords, err := rs.db.HasRecords(typ, (*EventRecord)(nil))
139
140
141
142
	if err != nil {
		return err
	}
	if hasRecords {
143
		return errors.New("revocation event record table not empty")
144
145
	}

146
	update, err := revocation.NewAccumulator(sk)
147
148
149
	if err != nil {
		return err
	}
150

151
	if err = rs.addUpdate(rs.db, typ, update, true); err != nil {
152
153
154
155
156
		return err
	}
	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
157
158
159
160
// RevocationEnabled returns whether or not revocation is enabled for the given credential type,
// by checking if any revocation record exists in the database.
func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bool, error) {
	if rs.sqlMode {
161
		return rs.db.HasRecords(typ, (*EventRecord)(nil))
Sietse Ringers's avatar
Sietse Ringers committed
162
163
164
165
166
	} else {
		return rs.memdb.HasRecords(typ), nil
	}
}

167
168
169
// Revocation update message methods

// UpdateFrom returns all records that a client requires to update its revocation state if it is currently
170
171
// at the specified index, that is, all records whose end index is greater than or equal to
// the specified index.
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, index uint64) (*revocation.Update, error) {
	// Only requires SQL implementation
	var update *revocation.Update
	if err := rs.db.Transaction(func(tx revStorage) error {
		acc, _, err := rs.currentAccumulator(tx, typ)
		if err != nil {
			return err
		}
		var events []*EventRecord
		if err := tx.From(typ, "index", index, &events); err != nil {
			return err
		}
		update = rs.newUpdate(acc, events)
		return nil
	}); err != nil {
		return nil, err
	}
	return update, nil
190
191
}

192
193
func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count uint64) (*revocation.Update, error) {
	// TODO what should this function and UpdateFrom return when no records are found?
194
	if rs.sqlMode {
195
196
197
198
199
200
201
		var update *revocation.Update
		if err := rs.db.Transaction(func(tx revStorage) error {
			acc, _, err := rs.currentAccumulator(tx, typ)
			if err != nil {
				return err
			}
			var events []*EventRecord
202
			if err := tx.Latest(typ, "eventindex", count, &events); err != nil {
203
204
205
206
207
				return err
			}
			update = rs.newUpdate(acc, events)
			return nil
		}); err != nil {
208
209
			return nil, err
		}
210
		return update, nil
211
	} else {
212
		return rs.memdb.Latest(typ, count), nil
213
214
215
	}
}

216
217
218
func (*RevocationStorage) newUpdate(acc *revocation.SignedAccumulator, events []*EventRecord) *revocation.Update {
	updates := make([]*revocation.Event, len(events))
	for i := range events {
219
		updates[i] = events[i].Event()
220
	}
221
222
223
	return &revocation.Update{
		SignedAccumulator: acc,
		Events:            updates,
224
	}
225
226
}

227
func (rs *RevocationStorage) AddUpdate(typ CredentialTypeIdentifier, record *revocation.Update) error {
228
	return rs.addUpdate(rs.db, typ, record, false)
229
}
230

231
func (rs *RevocationStorage) addUpdate(tx revStorage, typ CredentialTypeIdentifier, update *revocation.Update, create bool) error {
232
	// Unmarshal and verify the record against the appropriate public key
233
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), update.SignedAccumulator.PKIndex)
234
235
236
	if err != nil {
		return err
	}
237
	if _, _, err = update.Verify(pk, 0); err != nil {
238
239
240
		return err
	}

241
242
	// Save record
	if rs.sqlMode {
243
244
245
246
		save := tx.Save
		if create {
			save = tx.Insert
		}
247
		if err = save(new(AccumulatorRecord).Convert(typ, update.SignedAccumulator)); err != nil {
248
			return err
249
		}
250
		for _, event := range update.Events {
251
			if err = tx.Insert(new(EventRecord).Convert(typ, event)); err != nil {
252
253
254
				return err
			}
		}
255
	} else {
256
		rs.memdb.Insert(typ, update)
257
258
	}

259
	s := rs.getSettings(typ)
260
	s.updated = time.Now()
261
262
	// POST record to listeners, if any, asynchroniously
	go rs.client.PostUpdate(typ, s.PostURLs, update)
263
264
265

	return nil
}
266

267
268
269
270
// Issuance records

func (rs *RevocationStorage) IssuanceRecordExists(typ CredentialTypeIdentifier, key []byte) (bool, error) {
	return rs.db.Exists(typ, "key", key, &IssuanceRecord{})
271
272
}

273
274
func (rs *RevocationStorage) AddIssuanceRecord(r *IssuanceRecord) error {
	return rs.db.Insert(r)
275
276
}

277
278
279
280
281
func (rs *RevocationStorage) IssuanceRecord(typ CredentialTypeIdentifier, key []byte) (*IssuanceRecord, error) {
	var r IssuanceRecord
	err := rs.db.Get(typ, "key", key, &r)
	if err != nil {
		return nil, err
282
	}
283
284
	return &r, nil
}
285

286
287
// Revocation methods

Sietse Ringers's avatar
Sietse Ringers committed
288
289
// Revoke revokes the credential specified by key if found within the current database,
// by updating its revocation time to now, removing its revocation attribute from the current accumulator,
290
// and updating the revocation database on disk.
291
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string, sk *revocation.PrivateKey) error {
Sietse Ringers's avatar
Sietse Ringers committed
292
293
	if rs.getSettings(typ).Mode != RevocationModeServer {
		return errors.Errorf("cannot revoke %s", typ)
294
	}
295

296
297
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
298
		issrecord := IssuanceRecord{}
299
		if err = tx.Get(typ, "revocationkey", key, &issrecord); err != nil {
300
301
			return err
		}
302
303
		issrecord.RevokedAt = time.Now().UnixNano()
		if err = tx.Save(&issrecord); err != nil {
304
305
			return err
		}
306
		return rs.revokeAttr(tx, typ, sk, issrecord.Attr)
307
308
309
	})
}

310
func (rs *RevocationStorage) revokeAttr(tx revStorage, typ CredentialTypeIdentifier, sk *revocation.PrivateKey, e *RevocationAttribute) error {
311
	_, cur, err := rs.currentAccumulator(tx, typ)
312
313
314
315
316
317
	if err != nil {
		return err
	}
	if cur == nil {
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}
318
319
	var parent EventRecord
	if err = rs.db.Last(typ, &parent); err != nil {
320
321
		return err
	}
322

323
	update, err := cur.Remove(sk, (*big.Int)(e), parent.Event())
324
325
326
	if err != nil {
		return err
	}
327
	if err = rs.addUpdate(tx, typ, update, false); err != nil {
328
329
330
331
332
		return err
	}
	return nil
}

333
// Accumulator methods
334

335
336
337
338
func (rs *RevocationStorage) currentAccumulator(tx revStorage, typ CredentialTypeIdentifier) (
	*revocation.SignedAccumulator, *revocation.Accumulator, error,
) {
	var err error
339
	var sacc *revocation.SignedAccumulator
340
	if rs.sqlMode {
341
		record := &AccumulatorRecord{}
342
		if err = tx.Last(typ, record); err != nil {
343
			if gorm.IsRecordNotFoundError(err) {
344
				return nil, nil, nil
345
			}
346
		}
347
		sacc = record.SignedAccumulator()
348
	} else {
349
350
351
		u := rs.memdb.Latest(typ, 0)
		if u == nil {
			return nil, nil, nil
352
		}
353
		sacc = u.SignedAccumulator
354
355
	}

356
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKIndex)
357
	if err != nil {
358
		return nil, nil, err
359
	}
360
	acc, err := sacc.UnmarshalVerify(pk)
361
362
	if err != nil {
		return nil, nil, err
363
	}
364
	return sacc, acc, nil
365
366
}

367
368
369
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
370
	update, err := rs.client.FetchUpdateLatest(typ, revocationUpdateCount)
371
	if err != nil {
372
		return err
373
	}
374

375
	if err = rs.AddUpdate(typ, update); err != nil {
376
377
378
379
380
381
		return err
	}

	// bump updated even if no new records were added
	rs.getSettings(typ).updated = time.Now()
	return nil
382
383
}

Sietse Ringers's avatar
Sietse Ringers committed
384
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
385
	settings := rs.getSettings(typ)
386
	// update 10 seconds before the maximum, to stay below it
387
	if settings.updated.Before(time.Now().Add(time.Duration(-settings.MaxNonrevocationDuration+10) * time.Second)) {
388
		if err := rs.UpdateDB(typ); err != nil {
389
390
391
392
393
394
			return err
		}
	}
	return nil
}

395
396
// SaveIssuanceRecord either stores the issuance record locally, if we are the revocation server of
// the crecential type, or it signs and sends it to the remote revocation server.
397
398
399
400
401
402
403
404
405
func (rs *RevocationStorage) SaveIssuanceRecord(typ CredentialTypeIdentifier, rec *IssuanceRecord, sk *gabi.PrivateKey) error {
	credtype := rs.conf.CredentialTypes[typ]
	if credtype == nil {
		return errors.New("unknown credential type")
	}
	if !credtype.SupportsRevocation() {
		return errors.New("cannot save issuance record: credential type does not support revocation")
	}

406
	// Just store it if we are the revocation server for this credential type
407
408
	settings := rs.getSettings(typ)
	if settings.Mode == RevocationModeServer {
409
		return rs.AddIssuanceRecord(rec)
410
411
	}

412
	// We have to send it, sign it first
413
414
415
	if settings.ServerURL == "" {
		return errors.New("cannot send issuance record: no server_url configured")
	}
416
	rsk, err := sk.RevocationKey()
417
418
419
	if err != nil {
		return err
	}
420
	return rs.client.PostIssuanceRecord(typ, rsk, rec, settings.ServerURL)
421
422
}

423
424
// Misscelaneous methods

425
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
426
	var t *CredentialTypeIdentifier
427

428
429
	for typ, s := range settings {
		switch s.Mode {
430
431
432
433
		case RevocationModeServer:
			if s.ServerURL != "" {
				return errors.New("server_url cannot be combined with server mode")
			}
434
			t = &typ
435
436
437
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
438
		default:
439
440
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s", "%s", "%s")`,
				s.Mode, typ, RevocationModeRequestor, RevocationModeServer, RevocationModeProxy)
441
		}
442
	}
443
444
445
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
446
447
448
449
450
451
452

	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
453
		db, err := newSqlStorage(debug, dbtype, connstr)
454
		if err != nil {
455
			return err
456
		}
457
458
		rs.db = db
		rs.sqlMode = true
459
	}
460
461
462
463
464
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
465
466
467
468
469
470
	for id, settings := range rs.settings {
		if settings.MaxNonrevocationDuration != 0 && settings.MaxNonrevocationDuration < 30 {
			return errors.Errorf("max_nonrev_duration setting for %s must be at least 30 seconds, was %d",
				id, settings.MaxNonrevocationDuration)
		}
	}
471
472
473
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
474
475
}

476
477
478
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
479
480
481
482
	}
	return nil
}

483
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
484
485
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
486
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
487
488
	if len(b.Revocation) == 0 {
		return nil
489
	}
490
	var err error
491
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]*revocation.Update, len(b.Revocation))
492
	for _, credid := range b.Revocation {
493
494
495
		if !rs.conf.CredentialTypes[credid].SupportsRevocation() {
			return errors.Errorf("cannot request nonrevocation proof for %s: revocation not enabled in scheme")
		}
Sietse Ringers's avatar
Sietse Ringers committed
496
		if err = rs.UpdateIfOld(credid); err != nil {
497
498
499
500
501
502
503
504
505
506
507
508
			updated := rs.getSettings(credid).updated
			if !updated.IsZero() {
				Logger.Warnf("failed to fetch revocation updates for %s, nonrevocation is guaranteed only until %s ago:",
					credid, time.Now().Sub(updated).String())
				Logger.Warn(err)
			} else {
				Logger.Errorf("revocation is disabled for %s: failed to fetch revocation updates and none are known locally", credid)
				Logger.Warn(err)
				// We can offer no nonrevocation guarantees at all while the requestor explicitly
				// asked for it; fail the session by returning an error
				return err
			}
509
		}
510
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, revocationUpdateCount)
511
512
513
		if err != nil {
			return err
		}
514
	}
515
516
517
518
519
520
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
521
	}
522
523
524
525
526
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
527
528
}

529
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
530
	transport := NewHTTPTransport("")
531
	transport.Binary = true
532
	for _, url := range urls {
533
534
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
535
536
			Logger.Warn("error sending revocation update", err)
		}
537
	}
538
539
}

540
541
542
543
544
func (client RevocationClient) PostIssuanceRecord(typ CredentialTypeIdentifier, sk *revocation.PrivateKey, rec *IssuanceRecord, url string) error {
	message, err := signed.MarshalSign(sk.ECDSA, rec)
	if err != nil {
		return err
	}
545
	return NewHTTPTransport(url).Post(
546
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
547
548
549
550
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
551
552
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, index uint64) (*revocation.Update, error) {
	return client.fetchUpdate(typ, "updatefrom", index)
553
}
554

555
556
557
558
559
func (client RevocationClient) FetchUpdateLatest(typ CredentialTypeIdentifier, count uint64) (*revocation.Update, error) {
	return client.fetchUpdate(typ, "updatelatest", count)
}

func (client RevocationClient) fetchUpdate(typ CredentialTypeIdentifier, u string, i uint64) (*revocation.Update, error) {
560
561
562
563
564
565
566
	var (
		err       error
		errs      multierror.Error
		update    = &revocation.Update{}
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
567
568
	for _, url := range client.Conf.CredentialTypes[typ].RevocationServers {
		transport.Server = url
569
		err = transport.Get(fmt.Sprintf("revocation/%s/%s/%d", u, typ, i), &update)
570
		if err == nil {
571
			return update, nil
572
573
574
		} else {
			errs.Errors = append(errs.Errors, err)
		}
575
	}
576
	return nil, errors.WrapPrefix(errs, "failed to download revocation update", 0)
577
578
}

579
580
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier) (*revocation.PrivateKey, error) {
	sk, err := rs.Conf.PrivateKey(issid)
581
	if err != nil {
582
		return nil, err
583
584
	}
	if sk == nil {
585
		return nil, errors.Errorf("unknown private key: %s", issid)
586
	}
587
	revsk, err := sk.RevocationKey()
588
	if err != nil {
589
		return nil, err
590
	}
591
592
	return revsk, nil
}
593

594
595
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.Conf.PublicKey(issid, int(counter))
596
	if err != nil {
597
		return nil, err
598
	}
599
600
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
601
	}
602
603
604
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
605
	}
606
	return revpk, nil
607
}
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712

// Conversion methods to/from database structs, SQL table rows, gob

func (e *EventRecord) Event() *revocation.Event {
	return &revocation.Event{
		Index:      e.Index,
		E:          (*big.Int)(e.E),
		ParentHash: revocation.Hash(e.ParentHash),
	}
}

func (e *EventRecord) Convert(typ CredentialTypeIdentifier, event *revocation.Event) *EventRecord {
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
		PKIndex: a.PKIndex,
		Data:    signed.Message(a.Data),
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
		Data:     signedMessage(sacc.Data),
		PKIndex:  sacc.PKIndex,
		CredType: typ,
	}
	return a
}

func (signedMessage) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}

// Value implements driver.Valuer, for SQL marshaling (to []byte).
func (i *RevocationAttribute) Value() (driver.Value, error) {
	return (*big.Int)(i).Bytes(), nil
}

// Scan implements sql.Scanner, for SQL unmarshaling (from a []byte).
func (i *RevocationAttribute) Scan(src interface{}) error {
	b, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a byte slice")
	}
	(*big.Int)(i).SetBytes(b)
	return nil
}

func (RevocationAttribute) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}

func (i *RevocationAttribute) GobEncode() ([]byte, error) {
	return MarshalBinary((*big.Int)(i))
}

func (i *RevocationAttribute) GobDecode(data []byte) error {
	return UnmarshalBinary(data, (*big.Int)(i))
}

func (hash eventHash) Value() (driver.Value, error) {
	return hash[:], nil
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
	copy((*hash)[:], s)
	return nil
}

func (eventHash) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}