From 1ebbb92e6cc322e2be051db8acb3c8604455372a Mon Sep 17 00:00:00 2001 From: Fedor Partanskiy Date: Mon, 2 Sep 2024 16:30:28 +0300 Subject: [PATCH] add v2 with github.com/hyperledger/fabric-protos-go-apiv2 Signed-off-by: Fedor Partanskiy --- .gitignore | 1 + v2/audit_aries_test.go | 225 ++ v2/audit_legacy_test.go | 225 ++ v2/bccsp/aries_test.go | 1821 +++++++++++++++++ v2/bccsp/bccsp.go | 454 ++++ v2/bccsp/bccsp_test.go | 391 ++++ v2/bccsp/handlers/cred.go | 169 ++ v2/bccsp/handlers/cred_test.go | 498 +++++ v2/bccsp/handlers/idemix_suite_test.go | 28 + v2/bccsp/handlers/issuer.go | 172 ++ v2/bccsp/handlers/issuer_test.go | 269 +++ v2/bccsp/handlers/nym.go | 205 ++ v2/bccsp/handlers/nymsigner.go | 142 ++ v2/bccsp/handlers/nymsigner_test.go | 345 ++++ v2/bccsp/handlers/revocation.go | 250 +++ v2/bccsp/handlers/revocation_test.go | 429 ++++ v2/bccsp/handlers/signer.go | 177 ++ v2/bccsp/handlers/signer_test.go | 538 +++++ v2/bccsp/handlers/user.go | 98 + v2/bccsp/handlers/user_test.go | 421 ++++ v2/bccsp/idemix_suite_test.go | 18 + v2/bccsp/impl.go | 410 ++++ v2/bccsp/keystore/dummy.go | 34 + v2/bccsp/keystore/kvs/filebased.go | 69 + v2/bccsp/keystore/kvsbased.go | 121 ++ v2/bccsp/keystore/kvsbased_test.go | 81 + v2/bccsp/legacy_test.go | 1809 ++++++++++++++++ v2/bccsp/perf_test.go | 302 +++ .../schemes/dlog/bridge/bridge_suite_test.go | 24 + v2/bccsp/schemes/dlog/bridge/bridge_test.go | 1530 ++++++++++++++ v2/bccsp/schemes/dlog/bridge/credential.go | 130 ++ v2/bccsp/schemes/dlog/bridge/credrequest.go | 92 + v2/bccsp/schemes/dlog/bridge/issuer.go | 143 ++ .../schemes/dlog/bridge/nymsignaturescheme.go | 74 + v2/bccsp/schemes/dlog/bridge/rand.go | 20 + v2/bccsp/schemes/dlog/bridge/revocation.go | 76 + .../schemes/dlog/bridge/signaturescheme.go | 274 +++ v2/bccsp/schemes/dlog/bridge/user.go | 96 + v2/bccsp/schemes/dlog/crypto/credential.go | 220 ++ v2/bccsp/schemes/dlog/crypto/credrequest.go | 125 ++ v2/bccsp/schemes/dlog/crypto/idemix.go | 16 + v2/bccsp/schemes/dlog/crypto/idemix.pb.go | 897 ++++++++ v2/bccsp/schemes/dlog/crypto/idemix.proto | 150 ++ v2/bccsp/schemes/dlog/crypto/idemix_test.go | 856 ++++++++ v2/bccsp/schemes/dlog/crypto/issuerkey.go | 231 +++ v2/bccsp/schemes/dlog/crypto/logging.go | 37 + .../dlog/crypto/nonrevocation-prover.go | 47 + .../dlog/crypto/nonrevocation-verifier.go | 36 + v2/bccsp/schemes/dlog/crypto/nymeid.go | 59 + v2/bccsp/schemes/dlog/crypto/nymrh.go | 59 + v2/bccsp/schemes/dlog/crypto/nymsignature.go | 128 ++ .../dlog/crypto/revocation_authority.go | 136 ++ v2/bccsp/schemes/dlog/crypto/signature.go | 1190 +++++++++++ .../dlog/crypto/translator/amcl/amcl.pb.go | 164 ++ .../dlog/crypto/translator/amcl/amcl.proto | 32 + .../dlog/crypto/translator/amcl/fp256bn.go | 168 ++ .../crypto/translator/amcl/fp256bn_test.go | 158 ++ .../dlog/crypto/translator/amcl/gurvy.go | 64 + .../dlog/crypto/translator/amcl/gurvy_test.go | 75 + .../translator/amcl/testdata/old/g2.bytes | 1 + .../amcl/testdata/old/g2.proto.bytes | 2 + v2/bccsp/schemes/dlog/crypto/util.go | 71 + v2/bccsp/smartcard_test.go | 485 +++++ v2/bccsp/testdata/idemix/ca/IssuerPublicKey | 1 + v2/bccsp/testdata/idemix/ca/IssuerSecretKey | 1 + v2/bccsp/testdata/idemix/ca/RevocationKey | 6 + v2/bccsp/testdata/idemix/msp/IssuerPublicKey | 1 + .../testdata/idemix/msp/RevocationPublicKey | 5 + v2/bccsp/testdata/idemix/user/SignerConfig | Bin 0 -> 561 bytes v2/bccsp/testdata/old/cred_request.sign | 4 + v2/bccsp/testdata/old/credential.sign | Bin 0 -> 378 bytes v2/bccsp/testdata/old/cri.sign | Bin 0 -> 244 bytes v2/bccsp/testdata/old/issuer_nonce | 1 + v2/bccsp/testdata/old/issuerkey.pk | Bin 0 -> 906 bytes v2/bccsp/testdata/old/issuerkey.sk | Bin 0 -> 943 bytes v2/bccsp/testdata/old/nym_signature.sign | 2 + v2/bccsp/testdata/old/nymkey.pk | 1 + v2/bccsp/testdata/old/nymkey.sk | 1 + v2/bccsp/testdata/old/revocation.pk | Bin 0 -> 120 bytes v2/bccsp/testdata/old/revocation.sk | 1 + .../old/signature_no_disclosed_attribute.sign | Bin 0 -> 969 bytes .../signature_with_disclosed_attribute.sign | Bin 0 -> 901 bytes v2/bccsp/testdata/old/userkey.sk | 1 + v2/common/flogging/core.go | 125 ++ v2/common/flogging/core_test.go | 253 +++ v2/common/flogging/fabenc/color.go | 39 + v2/common/flogging/fabenc/color_test.go | 40 + v2/common/flogging/fabenc/encoder.go | 80 + v2/common/flogging/fabenc/encoder_test.go | 84 + v2/common/flogging/fabenc/formatter.go | 294 +++ v2/common/flogging/fabenc/formatter_test.go | 317 +++ v2/common/flogging/floggingtest/logger.go | 242 +++ .../flogging/floggingtest/logger_test.go | 92 + v2/common/flogging/global.go | 84 + v2/common/flogging/global_test.go | 162 ++ v2/common/flogging/levels.go | 68 + v2/common/flogging/levels_test.go | 74 + v2/common/flogging/loggerlevels.go | 175 ++ v2/common/flogging/loggerlevels_test.go | 193 ++ v2/common/flogging/logging.go | 250 +++ v2/common/flogging/logging_test.go | 158 ++ v2/common/flogging/mock/observer.go | 123 ++ v2/common/flogging/mock/write_syncer.go | 180 ++ v2/common/flogging/zap.go | 105 + v2/common/flogging/zap_test.go | 338 +++ v2/go.mod | 49 + v2/go.sum | 129 ++ v2/idemix_roles.go | 71 + v2/idemixmsp.go | 765 +++++++ v2/idemixmsp/identities.pb.go | 131 ++ v2/idemixmsp/identities.proto | 35 + v2/idemixmsp/msp_config.pb.go | 240 +++ v2/idemixmsp/msp_config.proto | 56 + v2/idemixmsp_aries_test.go | 636 ++++++ v2/idemixmsp_test.go | 771 +++++++ v2/msp.go | 219 ++ v2/testdata/aries/EidRH/ca/IssuerPublicKey | 1 + v2/testdata/aries/EidRH/ca/IssuerSecretKey | 1 + v2/testdata/aries/EidRH/ca/RevocationKey | 6 + v2/testdata/aries/EidRH/msp/IssuerPublicKey | 1 + .../aries/EidRH/msp/RevocationPublicKey | 5 + v2/testdata/aries/EidRH/user/SignerConfig | Bin 0 -> 640 bytes .../aries/MSP1OU1eid1/ca/IssuerPublicKey | 1 + .../aries/MSP1OU1eid1/ca/IssuerSecretKey | 1 + .../aries/MSP1OU1eid1/ca/RevocationKey | 6 + .../aries/MSP1OU1eid1/msp/IssuerPublicKey | 1 + .../aries/MSP1OU1eid1/msp/RevocationPublicKey | 5 + .../aries/MSP1OU1eid1/user/SignerConfig | Bin 0 -> 604 bytes .../aries/MSP1OU1eid1Admin/ca/IssuerPublicKey | 3 + .../aries/MSP1OU1eid1Admin/ca/IssuerSecretKey | 1 + .../aries/MSP1OU1eid1Admin/ca/RevocationKey | 6 + .../MSP1OU1eid1Admin/msp/IssuerPublicKey | 3 + .../MSP1OU1eid1Admin/msp/RevocationPublicKey | 5 + .../aries/MSP1OU1eid1Admin/user/SignerConfig | Bin 0 -> 604 bytes .../aries/MSP1Verifier/ca/IssuerPublicKey | 1 + .../aries/MSP1Verifier/ca/IssuerSecretKey | 1 + .../aries/MSP1Verifier/ca/RevocationKey | 6 + .../aries/MSP1Verifier/msp/IssuerPublicKey | 1 + .../MSP1Verifier/msp/RevocationPublicKey | 5 + .../aries/MSP2OU1eid1/ca/IssuerPublicKey | Bin 0 -> 96 bytes .../aries/MSP2OU1eid1/ca/IssuerSecretKey | Bin 0 -> 32 bytes .../aries/MSP2OU1eid1/ca/RevocationKey | 6 + .../aries/MSP2OU1eid1/msp/IssuerPublicKey | Bin 0 -> 96 bytes .../aries/MSP2OU1eid1/msp/RevocationPublicKey | 5 + .../aries/MSP2OU1eid1/user/SignerConfig | Bin 0 -> 603 bytes v2/testdata/idemix/EidRH/ca/IssuerPublicKey | Bin 0 -> 843 bytes v2/testdata/idemix/EidRH/ca/IssuerSecretKey | 1 + v2/testdata/idemix/EidRH/ca/RevocationKey | 6 + v2/testdata/idemix/EidRH/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../idemix/EidRH/msp/RevocationPublicKey | 5 + v2/testdata/idemix/EidRH/user/SignerConfig | Bin 0 -> 677 bytes v2/testdata/idemix/MSP1OU1/ca/IssuerPublicKey | 16 + v2/testdata/idemix/MSP1OU1/ca/IssuerSecretKey | 1 + v2/testdata/idemix/MSP1OU1/ca/RevocationKey | 6 + .../idemix/MSP1OU1/msp/IssuerPublicKey | 16 + .../idemix/MSP1OU1/msp/RevocationPublicKey | 5 + v2/testdata/idemix/MSP1OU1/user/SignerConfig | Bin 0 -> 644 bytes .../idemix/MSP1OU1Admin/ca/IssuerPublicKey | 16 + .../idemix/MSP1OU1Admin/ca/IssuerSecretKey | 1 + .../idemix/MSP1OU1Admin/ca/RevocationKey | 6 + .../idemix/MSP1OU1Admin/msp/IssuerPublicKey | 16 + .../MSP1OU1Admin/msp/RevocationPublicKey | 5 + .../idemix/MSP1OU1Admin/user/SignerConfig | Bin 0 -> 650 bytes v2/testdata/idemix/MSP1OU2/ca/IssuerPublicKey | 16 + v2/testdata/idemix/MSP1OU2/ca/IssuerSecretKey | 1 + v2/testdata/idemix/MSP1OU2/ca/RevocationKey | 6 + .../idemix/MSP1OU2/msp/IssuerPublicKey | 16 + .../idemix/MSP1OU2/msp/RevocationPublicKey | 5 + v2/testdata/idemix/MSP1OU2/user/SignerConfig | Bin 0 -> 644 bytes .../idemix/MSP1Verifier/ca/IssuerPublicKey | 16 + .../idemix/MSP1Verifier/ca/IssuerSecretKey | 1 + .../idemix/MSP1Verifier/ca/RevocationKey | 6 + .../idemix/MSP1Verifier/msp/IssuerPublicKey | 16 + .../MSP1Verifier/msp/RevocationPublicKey | 5 + v2/testdata/idemix/MSP2OU1/ca/IssuerPublicKey | Bin 0 -> 843 bytes v2/testdata/idemix/MSP2OU1/ca/IssuerSecretKey | 1 + v2/testdata/idemix/MSP2OU1/ca/RevocationKey | 6 + .../idemix/MSP2OU1/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../idemix/MSP2OU1/msp/RevocationPublicKey | 5 + v2/testdata/idemix/MSP2OU1/user/SignerConfig | Bin 0 -> 645 bytes 180 files changed, 24442 insertions(+) create mode 100644 v2/audit_aries_test.go create mode 100644 v2/audit_legacy_test.go create mode 100644 v2/bccsp/aries_test.go create mode 100644 v2/bccsp/bccsp.go create mode 100644 v2/bccsp/bccsp_test.go create mode 100644 v2/bccsp/handlers/cred.go create mode 100644 v2/bccsp/handlers/cred_test.go create mode 100644 v2/bccsp/handlers/idemix_suite_test.go create mode 100644 v2/bccsp/handlers/issuer.go create mode 100644 v2/bccsp/handlers/issuer_test.go create mode 100644 v2/bccsp/handlers/nym.go create mode 100644 v2/bccsp/handlers/nymsigner.go create mode 100644 v2/bccsp/handlers/nymsigner_test.go create mode 100644 v2/bccsp/handlers/revocation.go create mode 100644 v2/bccsp/handlers/revocation_test.go create mode 100644 v2/bccsp/handlers/signer.go create mode 100644 v2/bccsp/handlers/signer_test.go create mode 100644 v2/bccsp/handlers/user.go create mode 100644 v2/bccsp/handlers/user_test.go create mode 100644 v2/bccsp/idemix_suite_test.go create mode 100644 v2/bccsp/impl.go create mode 100644 v2/bccsp/keystore/dummy.go create mode 100644 v2/bccsp/keystore/kvs/filebased.go create mode 100644 v2/bccsp/keystore/kvsbased.go create mode 100644 v2/bccsp/keystore/kvsbased_test.go create mode 100644 v2/bccsp/legacy_test.go create mode 100644 v2/bccsp/perf_test.go create mode 100644 v2/bccsp/schemes/dlog/bridge/bridge_suite_test.go create mode 100644 v2/bccsp/schemes/dlog/bridge/bridge_test.go create mode 100644 v2/bccsp/schemes/dlog/bridge/credential.go create mode 100644 v2/bccsp/schemes/dlog/bridge/credrequest.go create mode 100644 v2/bccsp/schemes/dlog/bridge/issuer.go create mode 100644 v2/bccsp/schemes/dlog/bridge/nymsignaturescheme.go create mode 100644 v2/bccsp/schemes/dlog/bridge/rand.go create mode 100644 v2/bccsp/schemes/dlog/bridge/revocation.go create mode 100644 v2/bccsp/schemes/dlog/bridge/signaturescheme.go create mode 100644 v2/bccsp/schemes/dlog/bridge/user.go create mode 100644 v2/bccsp/schemes/dlog/crypto/credential.go create mode 100644 v2/bccsp/schemes/dlog/crypto/credrequest.go create mode 100644 v2/bccsp/schemes/dlog/crypto/idemix.go create mode 100644 v2/bccsp/schemes/dlog/crypto/idemix.pb.go create mode 100644 v2/bccsp/schemes/dlog/crypto/idemix.proto create mode 100644 v2/bccsp/schemes/dlog/crypto/idemix_test.go create mode 100644 v2/bccsp/schemes/dlog/crypto/issuerkey.go create mode 100644 v2/bccsp/schemes/dlog/crypto/logging.go create mode 100644 v2/bccsp/schemes/dlog/crypto/nonrevocation-prover.go create mode 100644 v2/bccsp/schemes/dlog/crypto/nonrevocation-verifier.go create mode 100644 v2/bccsp/schemes/dlog/crypto/nymeid.go create mode 100644 v2/bccsp/schemes/dlog/crypto/nymrh.go create mode 100644 v2/bccsp/schemes/dlog/crypto/nymsignature.go create mode 100644 v2/bccsp/schemes/dlog/crypto/revocation_authority.go create mode 100644 v2/bccsp/schemes/dlog/crypto/signature.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.pb.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn_test.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy_test.go create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.bytes create mode 100644 v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.proto.bytes create mode 100644 v2/bccsp/schemes/dlog/crypto/util.go create mode 100644 v2/bccsp/smartcard_test.go create mode 100644 v2/bccsp/testdata/idemix/ca/IssuerPublicKey create mode 100644 v2/bccsp/testdata/idemix/ca/IssuerSecretKey create mode 100644 v2/bccsp/testdata/idemix/ca/RevocationKey create mode 100644 v2/bccsp/testdata/idemix/msp/IssuerPublicKey create mode 100644 v2/bccsp/testdata/idemix/msp/RevocationPublicKey create mode 100644 v2/bccsp/testdata/idemix/user/SignerConfig create mode 100644 v2/bccsp/testdata/old/cred_request.sign create mode 100644 v2/bccsp/testdata/old/credential.sign create mode 100644 v2/bccsp/testdata/old/cri.sign create mode 100644 v2/bccsp/testdata/old/issuer_nonce create mode 100644 v2/bccsp/testdata/old/issuerkey.pk create mode 100644 v2/bccsp/testdata/old/issuerkey.sk create mode 100644 v2/bccsp/testdata/old/nym_signature.sign create mode 100644 v2/bccsp/testdata/old/nymkey.pk create mode 100644 v2/bccsp/testdata/old/nymkey.sk create mode 100644 v2/bccsp/testdata/old/revocation.pk create mode 100644 v2/bccsp/testdata/old/revocation.sk create mode 100644 v2/bccsp/testdata/old/signature_no_disclosed_attribute.sign create mode 100644 v2/bccsp/testdata/old/signature_with_disclosed_attribute.sign create mode 100644 v2/bccsp/testdata/old/userkey.sk create mode 100644 v2/common/flogging/core.go create mode 100644 v2/common/flogging/core_test.go create mode 100644 v2/common/flogging/fabenc/color.go create mode 100644 v2/common/flogging/fabenc/color_test.go create mode 100644 v2/common/flogging/fabenc/encoder.go create mode 100644 v2/common/flogging/fabenc/encoder_test.go create mode 100644 v2/common/flogging/fabenc/formatter.go create mode 100644 v2/common/flogging/fabenc/formatter_test.go create mode 100644 v2/common/flogging/floggingtest/logger.go create mode 100644 v2/common/flogging/floggingtest/logger_test.go create mode 100644 v2/common/flogging/global.go create mode 100644 v2/common/flogging/global_test.go create mode 100644 v2/common/flogging/levels.go create mode 100644 v2/common/flogging/levels_test.go create mode 100644 v2/common/flogging/loggerlevels.go create mode 100644 v2/common/flogging/loggerlevels_test.go create mode 100644 v2/common/flogging/logging.go create mode 100644 v2/common/flogging/logging_test.go create mode 100644 v2/common/flogging/mock/observer.go create mode 100644 v2/common/flogging/mock/write_syncer.go create mode 100644 v2/common/flogging/zap.go create mode 100644 v2/common/flogging/zap_test.go create mode 100644 v2/go.mod create mode 100644 v2/go.sum create mode 100644 v2/idemix_roles.go create mode 100644 v2/idemixmsp.go create mode 100644 v2/idemixmsp/identities.pb.go create mode 100644 v2/idemixmsp/identities.proto create mode 100644 v2/idemixmsp/msp_config.pb.go create mode 100644 v2/idemixmsp/msp_config.proto create mode 100644 v2/idemixmsp_aries_test.go create mode 100644 v2/idemixmsp_test.go create mode 100644 v2/msp.go create mode 100644 v2/testdata/aries/EidRH/ca/IssuerPublicKey create mode 100644 v2/testdata/aries/EidRH/ca/IssuerSecretKey create mode 100644 v2/testdata/aries/EidRH/ca/RevocationKey create mode 100644 v2/testdata/aries/EidRH/msp/IssuerPublicKey create mode 100644 v2/testdata/aries/EidRH/msp/RevocationPublicKey create mode 100644 v2/testdata/aries/EidRH/user/SignerConfig create mode 100644 v2/testdata/aries/MSP1OU1eid1/ca/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1/ca/IssuerSecretKey create mode 100644 v2/testdata/aries/MSP1OU1eid1/ca/RevocationKey create mode 100644 v2/testdata/aries/MSP1OU1eid1/msp/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1/msp/RevocationPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1/user/SignerConfig create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/ca/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/ca/IssuerSecretKey create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/ca/RevocationKey create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/msp/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/msp/RevocationPublicKey create mode 100644 v2/testdata/aries/MSP1OU1eid1Admin/user/SignerConfig create mode 100644 v2/testdata/aries/MSP1Verifier/ca/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1Verifier/ca/IssuerSecretKey create mode 100644 v2/testdata/aries/MSP1Verifier/ca/RevocationKey create mode 100644 v2/testdata/aries/MSP1Verifier/msp/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP1Verifier/msp/RevocationPublicKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/ca/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/ca/IssuerSecretKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/ca/RevocationKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/msp/IssuerPublicKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/msp/RevocationPublicKey create mode 100644 v2/testdata/aries/MSP2OU1eid1/user/SignerConfig create mode 100644 v2/testdata/idemix/EidRH/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/EidRH/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/EidRH/ca/RevocationKey create mode 100644 v2/testdata/idemix/EidRH/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/EidRH/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/EidRH/user/SignerConfig create mode 100644 v2/testdata/idemix/MSP1OU1/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/MSP1OU1/ca/RevocationKey create mode 100644 v2/testdata/idemix/MSP1OU1/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1/user/SignerConfig create mode 100644 v2/testdata/idemix/MSP1OU1Admin/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1Admin/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/MSP1OU1Admin/ca/RevocationKey create mode 100644 v2/testdata/idemix/MSP1OU1Admin/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1Admin/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/MSP1OU1Admin/user/SignerConfig create mode 100644 v2/testdata/idemix/MSP1OU2/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU2/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/MSP1OU2/ca/RevocationKey create mode 100644 v2/testdata/idemix/MSP1OU2/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1OU2/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/MSP1OU2/user/SignerConfig create mode 100644 v2/testdata/idemix/MSP1Verifier/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1Verifier/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/MSP1Verifier/ca/RevocationKey create mode 100644 v2/testdata/idemix/MSP1Verifier/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP1Verifier/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/MSP2OU1/ca/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP2OU1/ca/IssuerSecretKey create mode 100644 v2/testdata/idemix/MSP2OU1/ca/RevocationKey create mode 100644 v2/testdata/idemix/MSP2OU1/msp/IssuerPublicKey create mode 100644 v2/testdata/idemix/MSP2OU1/msp/RevocationPublicKey create mode 100644 v2/testdata/idemix/MSP2OU1/user/SignerConfig diff --git a/.gitignore b/.gitignore index ba077a4..84e7b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ bin +.idea diff --git a/v2/audit_aries_test.go b/v2/audit_aries_test.go new file mode 100644 index 0000000..63353b6 --- /dev/null +++ b/v2/audit_aries_test.go @@ -0,0 +1,225 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "testing" + + bccsp "github.com/IBM/idemix/bccsp/types" + im "github.com/IBM/idemix/v2/idemixmsp" + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +func TestAuditAries(t *testing.T) { + msp, err := NewIdemixMspAries(MSPv1_3) + assert.NoError(t, err) + + // fixtures in testdata/aries/EidRH were generated by running: + // idemixgen ca-keygen --aries + // idemixgen signerconfig --aries --ca-input=./idemix-config/ --enrollmentId=my-enrollment-id --revocationHandle=my-revocation-handle --org-unit=my-ou + + conf, err := GetIdemixMspConfigWithType("testdata/aries/EidRH", "EidRH", IDEMIX_ARIES) + assert.NoError(t, err) + + err = msp.Setup(conf) + assert.NoError(t, err) + + id, err := msp.GetDefaultSigningIdentity() + assert.NoError(t, err) + + idemixSigner := id.(*IdemixSigningIdentity) + + config := &im.IdemixMSPConfig{} + err = proto.Unmarshal(conf.Config, config) + assert.NoError(t, err) + + idemixMsp := msp.(*Idemixmsp) + csp := idemixMsp.csp + + msg := []byte("Lost forever, now and ever\nTo this magical sound that I hear") + + // STEP 1: Sign and Verify normally + + signature, err := csp.Sign( + idemixSigner.UserKey, + msg, + &bccsp.IdemixSignerOpts{ + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + }, + ) + assert.NoError(t, err) + + valid, err := csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 2: Sign by also generating a commitment to the EID (and Verify) + + sOpts := &bccsp.IdemixSignerOpts{ + SigType: bccsp.EidNym, + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + } + + signature, err = csp.Sign( + idemixSigner.UserKey, + msg, + sOpts, + ) + assert.NoError(t, err) + + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 3: audit of the nym eid + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.EidNymAuditOpts{ + EidIndex: AttributeIndexEnrollmentId, + EnrollmentID: config.Signer.EnrollmentId, + RNymEid: sOpts.Metadata.EidNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 4: Sign by also generating a commitment to the EID and to the RH (and Verify) + + sOpts = &bccsp.IdemixSignerOpts{ + SigType: bccsp.EidNymRhNym, + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + } + + signature, err = csp.Sign( + idemixSigner.UserKey, + msg, + sOpts, + ) + assert.NoError(t, err) + + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 5: audit of the nym eid + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.EidNymAuditOpts{ + EidIndex: AttributeIndexEnrollmentId, + EnrollmentID: config.Signer.EnrollmentId, + RNymEid: sOpts.Metadata.EidNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 6: audit of the rh + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.RhNymAuditOpts{ + RhIndex: AttributeIndexRevocationHandle, + RevocationHandle: config.Signer.RevocationHandle, + RNymRh: sOpts.Metadata.RhNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) +} diff --git a/v2/audit_legacy_test.go b/v2/audit_legacy_test.go new file mode 100644 index 0000000..1a9a888 --- /dev/null +++ b/v2/audit_legacy_test.go @@ -0,0 +1,225 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "testing" + + bccsp "github.com/IBM/idemix/bccsp/types" + im "github.com/IBM/idemix/v2/idemixmsp" + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +func TestAudit(t *testing.T) { + msp, err := NewIdemixMsp(MSPv1_3) + assert.NoError(t, err) + + // fixtures in testdata/idemix/EidRH were generated by running: + // idemixgen ca-keygen + // idemixgen signerconfig --ca-input=./idemix-config/ --enrollmentId=my-enrollment-id --revocationHandle=my-revocation-handle --org-unit=my-ou + + conf, err := GetIdemixMspConfigWithType("testdata/idemix/EidRH", "EidRH", IDEMIX) + assert.NoError(t, err) + + err = msp.Setup(conf) + assert.NoError(t, err) + + id, err := msp.GetDefaultSigningIdentity() + assert.NoError(t, err) + + idemixSigner := id.(*IdemixSigningIdentity) + + config := &im.IdemixMSPConfig{} + err = proto.Unmarshal(conf.Config, config) + assert.NoError(t, err) + + idemixMsp := msp.(*Idemixmsp) + csp := idemixMsp.csp + + msg := []byte("Lost forever, now and ever\nTo this magical sound that I hear") + + // STEP 1: Sign and Verify normally + + signature, err := csp.Sign( + idemixSigner.UserKey, + msg, + &bccsp.IdemixSignerOpts{ + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + }, + ) + assert.NoError(t, err) + + valid, err := csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 2: Sign by also generating a commitment to the EID (and Verify) + + sOpts := &bccsp.IdemixSignerOpts{ + SigType: bccsp.EidNym, + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + } + + signature, err = csp.Sign( + idemixSigner.UserKey, + msg, + sOpts, + ) + assert.NoError(t, err) + + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 3: audit of the nym eid + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.EidNymAuditOpts{ + EidIndex: AttributeIndexEnrollmentId, + EnrollmentID: config.Signer.EnrollmentId, + RNymEid: sOpts.Metadata.EidNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 4: Sign by also generating a commitment to the EID and to the RH (and Verify) + + sOpts = &bccsp.IdemixSignerOpts{ + SigType: bccsp.EidNymRhNym, + Credential: idemixSigner.Cred, + Nym: idemixSigner.NymKey, + IssuerPK: idemixMsp.ipk, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + CRI: config.Signer.CredentialRevocationInformation, + } + + signature, err = csp.Sign( + idemixSigner.UserKey, + msg, + sOpts, + ) + assert.NoError(t, err) + + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: idemixMsp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: AttributeIndexRevocationHandle, + EidIndex: AttributeIndexEnrollmentId, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 5: audit of the nym eid + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.EidNymAuditOpts{ + EidIndex: AttributeIndexEnrollmentId, + EnrollmentID: config.Signer.EnrollmentId, + RNymEid: sOpts.Metadata.EidNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + // STEP 6: audit of the rh + valid, err = csp.Verify( + idemixMsp.ipk, + signature, + msg, + &bccsp.RhNymAuditOpts{ + RhIndex: AttributeIndexRevocationHandle, + RevocationHandle: config.Signer.RevocationHandle, + RNymRh: sOpts.Metadata.RhNymAuditData.Rand, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) +} diff --git a/v2/bccsp/aries_test.go b/v2/bccsp/aries_test.go new file mode 100644 index 0000000..117bbee --- /dev/null +++ b/v2/bccsp/aries_test.go @@ -0,0 +1,1821 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix_test + +import ( + "crypto/rand" + "io/ioutil" + "os" + "path" + + "github.com/IBM/idemix/bccsp/schemes/aries" + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func testAries() { + curve := math.Curves[math.BLS12_381_BBS] + translator := &amcl.Gurvy{C: curve} + + Describe("setting up the environment with one issuer and one user with curve", func() { + var ( + CSP bccsp.BCCSP + IssuerKey bccsp.Key + IssuerPublicKey bccsp.Key + AttributeNames []string + + UserKey bccsp.Key + NymKey bccsp.Key + NymPublicKey bccsp.Key + + IssuerNonce []byte + credRequest []byte + + credential []byte + + RevocationKey bccsp.Key + RevocationPublicKey bccsp.Key + cri []byte + rootDir string + + blindCredReqOpts *bccsp.IdemixBlindCredentialRequestSignerOpts + ) + + BeforeEach(func() { + var err error + + rootDir, err = ioutil.TempDir(os.TempDir(), "idemixtest") + Expect(err).NotTo(HaveOccurred()) + + CSP, err = idemix.NewAries(NewDummyKeyStore(), curve, translator, true) + Expect(err).NotTo(HaveOccurred()) + + // Issuer + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + IssuerKey, err = CSP.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err := IssuerKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "issuerkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = IssuerPublicKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "issuerkey.pk"), raw, 0666)).NotTo(HaveOccurred()) + + // User + UserKey, err = CSP.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + raw, err = UserKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + // Expect(len(raw)).To(Equal(32)) + Expect(ioutil.WriteFile(path.Join(rootDir, "userkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + + // User Nym Key + NymKey, err = CSP.KeyDeriv(UserKey, &bccsp.IdemixNymKeyDerivationOpts{Temporary: true, IssuerPK: IssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = NymKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err = NymKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "nymkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = NymPublicKey.Bytes() + Expect(len(raw)).To(Equal(2 * curve.CoordByteSize)) + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "nymkey.pk"), raw, 0666)).NotTo(HaveOccurred()) + + IssuerNonce = make([]byte, curve.ScalarByteSize) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(curve.ScalarByteSize)) + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + blindCredReqOpts = &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce} + credRequest, err = CSP.Sign( + UserKey, + nil, + blindCredReqOpts, + ) + Expect(err).NotTo(HaveOccurred()) + + // Credential + credential, err = CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + // Revocation + RevocationKey, err = CSP.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err = RevocationKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "revocation.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = RevocationPublicKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "revocation.pk"), raw, 0666)).NotTo(HaveOccurred()) + + // CRI + cri, err = CSP.Sign( + RevocationKey, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + + cr := &aries.CredRequest{ + Curve: curve, + } + + credential, err = cr.Unblind(credential, blindCredReqOpts.Blinding) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CSP.Verify( + RevocationPublicKey, + cri, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Describe("producing an idemix signature with no disclosed attribute", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid even when there's garbage in the nym eid field if we do basic verification", func() { + sig := &aries.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.NymEid = []byte("invalid garbage") + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do ExpectStandard verification", func() { + sig := &aries.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.NymEid = []byte("invalid garbage") + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("EidNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do ExpectEidNym verification", func() { + sig := &aries.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.NymEid = []byte("invalid garbage") + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no EidNym provided but ExpectEidNym required")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do BestEffort verification", func() { + sig := &aries.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.NymEid = curve.GenG1.Bytes() + sig.NymEidProof = append(curve.GenG1.Compressed(), []byte{0, 0, 0, 0}...) + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("contribution is not zero")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect a signature with nym eid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no EidNym provided but ExpectEidNym required")) + Expect(valid).To(BeFalse()) + }) + + }) + + Describe("producing an idemix signature with an eid nym", func() { + var ( + digest []byte + signature []byte + signOpts *bccsp.IdemixSignerOpts + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signOpts = &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNym, + } + + signature, err = CSP.Sign( + UserKey, + digest, + signOpts, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts.Metadata).NotTo(BeNil()) + }) + + It("the signature is not valid if we use basic verification", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("contribution is not zero")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect an eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect an eid nym and supply the right one", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect an eid nym and supply the wrong one", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: curve.GenG1.Bytes(), + }, + }, + ) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, signature nym eid does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect an eid nym and supply garbage", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: []byte("garbage"), + }, + }, + ) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, failed to unmarshal meta nym eid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect an eid nym and request auditing of the eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect an eid nym and request auditing of the eid nym with a wrong randomness", func() { + signOpts.Metadata.EidNymAuditData.Rand = signOpts.Metadata.EidNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("EidNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("nym eid auditing with the right enrollment ID succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("valid signature against meta", func() { + signOpts2 := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNym, + Metadata: signOpts.Metadata, + } + signature2, err := CSP.Sign( + UserKey, + digest, + signOpts2, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts2.Metadata).NotTo(BeNil()) + + Expect(signOpts2.Metadata.EidNymAuditData.Nym.Equals(signOpts.Metadata.EidNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Attr.Equals(signOpts2.Metadata.EidNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Rand.Equals(signOpts.Metadata.EidNymAuditData.Rand)).To(BeTrue()) + + valid, err := CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts2.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with an eid nym and rh nym", func() { + var ( + digest []byte + signature []byte + signOpts *bccsp.IdemixSignerOpts + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signOpts = &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNymRhNym, + } + + signature, err = CSP.Sign( + UserKey, + digest, + signOpts, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts.Metadata).NotTo(BeNil()) + }) + + It("the signature is not valid if we use basic verification", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("contribution is not zero")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect only an eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("contribution is not zero")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect both an eid nym and rh nym and request auditing of the eid nym and the rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect both an eid nym and rh nym and supply the right eid nym and rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: signOpts.Metadata.RhNym, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the right eid nym and the wrong rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: curve.GenG1.Bytes(), + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: rh nym validation failed, signature rh nym does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the right eid nym and garbage rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: []byte("garbage"), + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: rh nym validation failed, failed to unmarshal meta rh nym")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the wrong eid nym and the right rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: curve.GenG1.Bytes(), + RhNym: signOpts.Metadata.RhNym, + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, signature nym eid does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and request auditing of the eid nym with a wrong randomness", func() { + signOpts.Metadata.EidNymAuditData.Rand = signOpts.Metadata.EidNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and request auditing of the rh nym with a wrong randomness", func() { + signOpts.Metadata.RhNymAuditData.Rand = signOpts.Metadata.RhNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym rh validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("RhNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("nym eid auditing with the right enrollment ID succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("nym rh auditing with the right revocation handle succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.RhNymAuditOpts{ + RhIndex: 4, + RevocationHandle: string([]byte{0, 1, 2, 3}), + RNymRh: signOpts.Metadata.RhNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.RhNymAuditData.Nym.Bytes(), + digest, + &bccsp.RhNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNymRhNym, + RhIndex: 4, + RevocationHandle: string([]byte{0, 1, 2, 3}), + RNymRh: signOpts.Metadata.RhNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("valid signature against meta", func() { + signOpts2 := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNymRhNym, + Metadata: signOpts.Metadata, + } + signature2, err := CSP.Sign( + UserKey, + digest, + signOpts2, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts2.Metadata).NotTo(BeNil()) + + Expect(signOpts2.Metadata.EidNymAuditData.Nym.Equals(signOpts.Metadata.EidNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Attr.Equals(signOpts2.Metadata.EidNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Rand.Equals(signOpts.Metadata.EidNymAuditData.Rand)).To(BeTrue()) + + Expect(signOpts2.Metadata.RhNymAuditData.Nym.Equals(signOpts.Metadata.RhNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.RhNymAuditData.Attr.Equals(signOpts2.Metadata.RhNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.RhNymAuditData.Rand.Equals(signOpts.Metadata.RhNymAuditData.Rand)).To(BeTrue()) + + valid, err := CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts2.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with disclosed attributes", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix nym signature", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixNymSignerOpts{ + Nym: NymKey, + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + NymPublicKey, + signature, + digest, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("Idemix Bridge Load", func() { + + Describe("setting up the environment with one issuer and one user", func() { + var ( + CSP bccsp.BCCSP + IssuerKey bccsp.Key + IssuerPublicKey bccsp.Key + AttributeNames []string + + UserKey bccsp.Key + NymKey bccsp.Key + NymPublicKey bccsp.Key + + IssuerNonce []byte + credRequest []byte + + credential []byte + + RevocationKey bccsp.Key + RevocationPublicKey bccsp.Key + cri []byte + ) + + BeforeEach(func() { + var err error + CSP, err = idemix.NewAries(NewDummyKeyStore(), curve, translator, true) + Expect(err).NotTo(HaveOccurred()) + + // Issuer + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + raw, err := ioutil.ReadFile(path.Join(rootDir, "issuerkey.sk")) + Expect(err).NotTo(HaveOccurred()) + IssuerKey, err = CSP.KeyImport(raw, &bccsp.IdemixIssuerKeyImportOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // User + raw, err = ioutil.ReadFile(path.Join(rootDir, "userkey.sk")) + Expect(err).NotTo(HaveOccurred()) + UserKey, err = CSP.KeyImport(raw, &bccsp.IdemixUserSecretKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + // User Nym Key + rawNymKeySk, err := ioutil.ReadFile(path.Join(rootDir, "nymkey.sk")) + Expect(err).NotTo(HaveOccurred()) + rawNymKeyPk, err := ioutil.ReadFile(path.Join(rootDir, "nymkey.pk")) + Expect(err).NotTo(HaveOccurred()) + Expect(len(rawNymKeyPk)).To(Equal(2 * curve.CoordByteSize)) + + NymKey, err = CSP.KeyImport(append(rawNymKeySk, rawNymKeyPk...), &bccsp.IdemixNymKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = CSP.KeyImport(rawNymKeyPk, &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + IssuerNonce = make([]byte, curve.ScalarByteSize) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(curve.ScalarByteSize)) + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + blindCredReqOpts = &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce} + credRequest, err = CSP.Sign( + UserKey, + nil, + blindCredReqOpts, + ) + Expect(err).NotTo(HaveOccurred()) + + // Credential + credential, err = CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + // Revocation + raw, err = ioutil.ReadFile(path.Join(rootDir, "revocation.sk")) + Expect(err).NotTo(HaveOccurred()) + RevocationKey, err = CSP.KeyImport(raw, &bccsp.IdemixRevocationKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // CRI + cri, err = CSP.Sign( + RevocationKey, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + + cr := &aries.CredRequest{ + Curve: curve, + } + + credential, err = cr.Unblind(credential, blindCredReqOpts.Blinding) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CSP.Verify( + RevocationPublicKey, + cri, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Describe("producing an idemix signature with no disclosed attribute", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with disclosed attributes", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix nym signature", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixNymSignerOpts{ + Nym: NymKey, + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + NymPublicKey, + signature, + digest, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + }) + }) + }) +} diff --git a/v2/bccsp/bccsp.go b/v2/bccsp/bccsp.go new file mode 100644 index 0000000..e49b58e --- /dev/null +++ b/v2/bccsp/bccsp.go @@ -0,0 +1,454 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix + +import ( + "reflect" + + "github.com/IBM/idemix/bccsp/schemes/aries" + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/bridge" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/hyperledger/aries-bbs-go/bbs" + "github.com/pkg/errors" +) + +type csp struct { + *CSP +} + +func New(keyStore bccsp.KeyStore, curve *math.Curve, translator idemix.Translator, exportable bool) (*csp, error) { + base, err := NewImpl(keyStore) + if err != nil { + return nil, errors.Wrap(err, "failed instantiating base bccsp") + } + + csp := &csp{CSP: base} + + idmx := &idemix.Idemix{ + Curve: curve, + Translator: translator, + } + + // key generators + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerKeyGenOpts{}), + &handlers.IssuerKeyGen{ + Exportable: exportable, + Issuer: &bridge.Issuer{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixUserSecretKeyGenOpts{}), + &handlers.UserKeyGen{ + Exportable: exportable, + User: &bridge.User{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationKeyGenOpts{}), + &handlers.RevocationKeyGen{ + Exportable: exportable, + Revocation: &bridge.Revocation{ + Idemix: idmx, Translator: translator, + }, + }) + + // key derivers + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &handlers.NymKeyDerivation{ + Exportable: exportable, + Translator: translator, + User: &bridge.User{ + Idemix: idmx, Translator: translator, + }, + }) + + // signers + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &userSecreKeySignerMultiplexer{ + signer: &handlers.Signer{ + SignatureScheme: &bridge.SignatureScheme{ + Idemix: idmx, Translator: translator, + }}, + nymSigner: &handlers.NymSigner{ + NymSignatureScheme: &bridge.NymSignatureScheme{ + Idemix: idmx, Translator: translator, + }}, + credentialRequestSigner: &handlers.CredentialRequestSigner{ + CredRequest: &bridge.CredRequest{ + Idemix: idmx, Translator: translator, + }}, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewIssuerSecretKey(nil, true)), + &handlers.CredentialSigner{ + Credential: &bridge.Credential{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewRevocationSecretKey(nil, true)), + &handlers.CriSigner{ + Revocation: &bridge.Revocation{ + Idemix: idmx, Translator: translator, + }, + }) + + // verifiers + base.AddWrapper(reflect.TypeOf(handlers.NewIssuerPublicKey(nil)), + &issuerPublicKeyVerifierMultiplexer{ + verifier: &handlers.Verifier{ + SignatureScheme: &bridge.SignatureScheme{ + Idemix: idmx, Translator: translator, + }}, + credentialRequestVerifier: &handlers.CredentialRequestVerifier{ + CredRequest: &bridge.CredRequest{ + Idemix: idmx, Translator: translator, + }}, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewNymPublicKey(nil, translator)), + &handlers.NymVerifier{ + NymSignatureScheme: &bridge.NymSignatureScheme{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &handlers.CredentialVerifier{ + Credential: &bridge.Credential{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewRevocationPublicKey(nil)), + &handlers.CriVerifier{ + Revocation: &bridge.Revocation{ + Idemix: idmx, Translator: translator, + }, + }) + + // importers + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixUserSecretKeyImportOpts{}), + &handlers.UserKeyImporter{ + Exportable: exportable, + User: &bridge.User{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerPublicKeyImportOpts{}), + &handlers.IssuerPublicKeyImporter{ + Issuer: &bridge.Issuer{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerKeyImportOpts{}), + &handlers.IssuerKeyImporter{ + Exportable: exportable, + Issuer: &bridge.Issuer{ + Idemix: idmx, Translator: translator, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixNymPublicKeyImportOpts{}), + &handlers.NymPublicKeyImporter{ + User: &bridge.User{ + Idemix: idmx, Translator: translator, + }, + Translator: translator, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixNymKeyImportOpts{}), + &handlers.NymKeyImporter{ + Exportable: exportable, + User: &bridge.User{ + Idemix: idmx, Translator: translator, + }, + Translator: translator, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationPublicKeyImportOpts{}), + &handlers.RevocationPublicKeyImporter{}) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationKeyImportOpts{}), + &handlers.RevocationKeyImporter{ + Exportable: exportable, + Revocation: &bridge.Revocation{ + Idemix: idmx, Translator: translator, + }, + }) + + return csp, nil +} + +func NewAries(keyStore bccsp.KeyStore, curve *math.Curve, _translator idemix.Translator, exportable bool) (*csp, error) { + base, err := NewImpl(keyStore) + if err != nil { + return nil, errors.Wrap(err, "failed instantiating base bccsp") + } + + csp := &csp{CSP: base} + + rng, err := curve.Rand() + if err != nil { + return nil, errors.Wrap(err, "failed instantiating PRNG") + } + + // key generators + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerKeyGenOpts{}), + &handlers.IssuerKeyGen{ + Exportable: exportable, + Issuer: &aries.Issuer{ + Curve: curve, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixUserSecretKeyGenOpts{}), + &handlers.UserKeyGen{ + Exportable: exportable, + User: &aries.User{ + Curve: curve, + Rng: rng, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationKeyGenOpts{}), + &handlers.RevocationKeyGen{ + Exportable: exportable, + Revocation: &aries.RevocationAuthority{ + Curve: curve, + Rng: rng, + }, + }) + + // key derivers + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &handlers.NymKeyDerivation{ + Exportable: exportable, + Translator: _translator, + User: &aries.User{ + Curve: curve, + Rng: rng, + }, + }) + + // signers + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &userSecreKeySignerMultiplexer{ + signer: &handlers.Signer{ + SignatureScheme: &aries.Signer{ + Curve: curve, + Rng: rng, + }}, + nymSigner: &handlers.NymSigner{ + SmartcardNymSignatureScheme: &aries.SmartcardIdemixBackend{ + Curve: curve, + }, + NymSignatureScheme: &aries.NymSigner{ + Curve: curve, + Rng: rng, + }}, + blindCredentialRequestSigner: &handlers.BlindCredentialRequestSigner{ + CredRequest: &aries.CredRequest{ + Curve: curve, + }}, + credentialRequestSigner: nil, // aries does not implement this approach + }) + base.AddWrapper(reflect.TypeOf(handlers.NewIssuerSecretKey(nil, true)), + &handlers.CredentialSigner{ + Credential: &aries.Cred{ + Curve: curve, + BBS: bbs.New(curve), + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewRevocationSecretKey(nil, true)), + &handlers.CriSigner{ + Revocation: &aries.RevocationAuthority{ + Curve: curve, + Rng: rng, + }, + }) + + // verifiers + base.AddWrapper(reflect.TypeOf(handlers.NewIssuerPublicKey(nil)), + &issuerPublicKeyVerifierMultiplexer{ + verifier: &handlers.Verifier{ + SignatureScheme: &aries.Signer{ + Curve: curve, + Rng: rng, + }}, + blindcredentialRequestVerifier: &handlers.BlindCredentialRequestVerifier{ + CredRequest: &aries.CredRequest{ + Curve: curve, + }}, + credentialRequestVerifier: nil, // aries does not implement this type of issuance + }) + base.AddWrapper(reflect.TypeOf(handlers.NewNymPublicKey(nil, _translator)), + &handlers.NymVerifier{ + SmartcardNymSignatureScheme: &aries.SmartcardIdemixBackend{ + Curve: curve, + }, + NymSignatureScheme: &aries.NymSigner{ + Curve: curve, + Rng: rng, + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewUserSecretKey(nil, true)), + &handlers.CredentialVerifier{ + Credential: &aries.Cred{ + Curve: curve, + BBS: bbs.New(curve), + }, + }) + base.AddWrapper(reflect.TypeOf(handlers.NewRevocationPublicKey(nil)), + &handlers.CriVerifier{ + Revocation: &aries.RevocationAuthority{ + Curve: curve, + Rng: rng, + }, + }) + + // importers + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixUserSecretKeyImportOpts{}), + &handlers.UserKeyImporter{ + Exportable: exportable, + User: &aries.User{ + Curve: curve, + Rng: rng, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerPublicKeyImportOpts{}), + &handlers.IssuerPublicKeyImporter{ + Issuer: &aries.Issuer{ + Curve: curve, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixIssuerKeyImportOpts{}), + &handlers.IssuerKeyImporter{ + Exportable: exportable, + Issuer: &aries.Issuer{ + Curve: curve, + }, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixNymPublicKeyImportOpts{}), + &handlers.NymPublicKeyImporter{ + User: &aries.User{ + Curve: curve, + Rng: rng, + }, + Translator: _translator, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixNymKeyImportOpts{}), + &handlers.NymKeyImporter{ + Exportable: exportable, + User: &aries.User{ + Curve: curve, + Rng: rng, + }, + Translator: _translator, + }) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationPublicKeyImportOpts{}), + &handlers.RevocationPublicKeyImporter{}) + base.AddWrapper(reflect.TypeOf(&bccsp.IdemixRevocationKeyImportOpts{}), + &handlers.RevocationKeyImporter{ + Exportable: exportable, + Revocation: &aries.RevocationAuthority{ + Curve: curve, + Rng: rng, + }, + }) + + return csp, nil +} + +// Sign signs digest using key k. +// The opts argument should be appropriate for the primitive used. +// +// Note that when a signature of a hash of a larger message is needed, +// the caller is responsible for hashing the larger message and passing +// the hash (as digest). +// Notice that this is overriding the Sign methods of the sw impl. to avoid the digest check. +func (csp *csp) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + // Do not check for digest + + keyType := reflect.TypeOf(k) + signer, found := csp.Signers[keyType] + if !found { + return nil, errors.Errorf("Unsupported 'SignKey' provided [%s]", keyType) + } + + signature, err = signer.Sign(k, digest, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed signing with opts [%v]", opts) + } + + return +} + +// Verify verifies signature against key k and digest +// Notice that this is overriding the Sign methods of the sw impl. to avoid the digest check. +func (csp *csp) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) { + // Validate arguments + if k == nil { + return false, errors.New("Invalid Key. It must not be nil.") + } + if len(signature) == 0 { + return false, errors.New("Invalid signature. Cannot be empty.") + } + // Do not check for digest + + verifier, found := csp.Verifiers[reflect.TypeOf(k)] + if !found { + return false, errors.Errorf("Unsupported 'VerifyKey' provided [%v]", k) + } + + valid, err = verifier.Verify(k, signature, digest, opts) + if err != nil { + return false, errors.Wrapf(err, "Failed verifing with opts [%v]", opts) + } + + return +} + +type userSecreKeySignerMultiplexer struct { + signer *handlers.Signer + nymSigner *handlers.NymSigner + credentialRequestSigner *handlers.CredentialRequestSigner + blindCredentialRequestSigner *handlers.BlindCredentialRequestSigner +} + +func (s *userSecreKeySignerMultiplexer) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) { + switch opts.(type) { + case *bccsp.IdemixSignerOpts: + return s.signer.Sign(k, digest, opts) + case *bccsp.IdemixNymSignerOpts: + return s.nymSigner.Sign(k, digest, opts) + case *bccsp.IdemixCredentialRequestSignerOpts: + return s.credentialRequestSigner.Sign(k, digest, opts) + case *bccsp.IdemixBlindCredentialRequestSignerOpts: + return s.blindCredentialRequestSigner.Sign(k, digest, opts) + default: + return nil, errors.New("invalid opts, expected *bccsp.IdemixSignerOpt or *bccsp.IdemixNymSignerOpts or *bccsp.IdemixCredentialRequestSignerOpts") + } +} + +type issuerPublicKeyVerifierMultiplexer struct { + verifier *handlers.Verifier + credentialRequestVerifier *handlers.CredentialRequestVerifier + blindcredentialRequestVerifier *handlers.BlindCredentialRequestVerifier +} + +func (v *issuerPublicKeyVerifierMultiplexer) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) { + switch opts.(type) { + case *bccsp.EidNymAuditOpts: + return v.verifier.AuditNymEid(k, signature, digest, opts) + case *bccsp.RhNymAuditOpts: + return v.verifier.AuditNymRh(k, signature, digest, opts) + case *bccsp.IdemixSignerOpts: + return v.verifier.Verify(k, signature, digest, opts) + case *bccsp.IdemixCredentialRequestSignerOpts: + return v.credentialRequestVerifier.Verify(k, signature, digest, opts) + case *bccsp.IdemixBlindCredentialRequestSignerOpts: + return v.blindcredentialRequestVerifier.Verify(k, signature, digest, opts) + default: + return false, errors.New("invalid opts, expected *bccsp.IdemixSignerOpts or *bccsp.IdemixCredentialRequestSignerOpts") + } +} diff --git a/v2/bccsp/bccsp_test.go b/v2/bccsp/bccsp_test.go new file mode 100644 index 0000000..b6403b3 --- /dev/null +++ b/v2/bccsp/bccsp_test.go @@ -0,0 +1,391 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix_test + +import ( + "fmt" + "io/ioutil" + + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/errors" +) + +// NewDummyKeyStore instantiate a dummy key store +// that neither loads nor stores keys +func NewDummyKeyStore() bccsp.KeyStore { + return &dummyKeyStore{} +} + +// dummyKeyStore is a read-only KeyStore that neither loads nor stores keys. +type dummyKeyStore struct { +} + +// ReadOnly returns true if this KeyStore is read only, false otherwise. +// If ReadOnly is true then StoreKey will fail. +func (ks *dummyKeyStore) ReadOnly() bool { + return true +} + +// GetKey returns a key object whose SKI is the one passed. +func (ks *dummyKeyStore) GetKey(ski []byte) (bccsp.Key, error) { + return nil, errors.New("Key not found. This is a dummy KeyStore") +} + +// StoreKey stores the key k in this KeyStore. +// If this KeyStore is read only then the method will fail. +func (ks *dummyKeyStore) StoreKey(k bccsp.Key) error { + return errors.New("Cannot store key. This is a dummy read-only KeyStore") +} + +var _ = Describe("Idemix Bridge", func() { + testWithCurve(math.FP256BN_AMCL, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}) + testWithCurve(math.BN254, &amcl.Gurvy{C: math.Curves[math.BN254]}) + testWithCurve(math.FP256BN_AMCL_MIRACL, &amcl.Fp256bnMiracl{C: math.Curves[math.FP256BN_AMCL_MIRACL]}) + testWithCurve(math.BLS12_381, &amcl.Gurvy{C: math.Curves[math.BLS12_381]}) + testWithCurve(math.BLS12_377_GURVY, &amcl.Gurvy{C: math.Curves[math.BLS12_377_GURVY]}) + testWithCurve(math.BLS12_381_GURVY, &amcl.Gurvy{C: math.Curves[math.BLS12_381_GURVY]}) +}) + +func curveName(id math.CurveID) string { + switch id { + case math.FP256BN_AMCL: + return "FP256BN_AMCL" + case math.BN254: + return "BN254" + case math.FP256BN_AMCL_MIRACL: + return "FP256BN_AMCL_MIRACL" + case math.BLS12_381: + return "BLS12_381" + case math.BLS12_377_GURVY: + return "BLS12_377_GURVY" + case math.BLS12_381_GURVY: + return "BLS12_381_GURVY" + default: + panic(fmt.Sprintf("unknown curve %d", id)) + } +} + +var _ = Describe("aries test", func() { + testAries() +}) + +var _ = Describe("Idemix Bridge Compatibility", func() { + + Describe("setting up the environment with one issuer and one user", func() { + var ( + CSP bccsp.BCCSP + IssuerKey bccsp.Key + IssuerPublicKey bccsp.Key + AttributeNames []string + + UserKey bccsp.Key + // NymKey bccsp.Key + NymPublicKey bccsp.Key + + IssuerNonce []byte + credRequest []byte + + credential []byte + + RevocationKey bccsp.Key + RevocationPublicKey bccsp.Key + cri []byte + ) + + BeforeEach(func() { + curve := math.Curves[math.FP256BN_AMCL] + translator := &amcl.Fp256bn{C: curve} + + var err error + CSP, err = idemix.New(NewDummyKeyStore(), curve, translator, true) + Expect(err).NotTo(HaveOccurred()) + + // Issuer + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + raw, err := ioutil.ReadFile("./testdata/old/issuerkey.sk") + Expect(err).NotTo(HaveOccurred()) + IssuerKey, err = CSP.KeyImport(raw, &bccsp.IdemixIssuerKeyImportOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // User + raw, err = ioutil.ReadFile("./testdata/old/userkey.sk") + Expect(err).NotTo(HaveOccurred()) + UserKey, err = CSP.KeyImport(raw, &bccsp.IdemixUserSecretKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + // User Nym Key + // rawNymKeySk, err := ioutil.ReadFile("./testdata/old/nymkey.sk") + // Expect(err).NotTo(HaveOccurred()) + rawNymKeyPk, err := ioutil.ReadFile("./testdata/old/nymkey.pk") + Expect(err).NotTo(HaveOccurred()) + + // NymKey, err = CSP.KeyImport(append(rawNymKeySk, rawNymKeyPk...), &bccsp.IdemixNymKeyImportOpts{Temporary: true}) + // Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = CSP.KeyImport(rawNymKeyPk, &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + // IssuerNonce = make([]byte, 32) + // n, err := rand.Read(IssuerNonce) + // Expect(n).To(BeEquivalentTo(32)) + // Expect(err).NotTo(HaveOccurred()) + IssuerNonce, err = ioutil.ReadFile("./testdata/old/issuer_nonce") + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + credRequest, err = ioutil.ReadFile("./testdata/old/cred_request.sign") + Expect(err).NotTo(HaveOccurred()) + // credRequest, err = CSP.Sign( + // UserKey, + // nil, + // &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce}, + // ) + // Expect(err).NotTo(HaveOccurred()) + + // Credential + // credential, err = CSP.Sign( + // IssuerKey, + // credRequest, + // &bccsp.IdemixCredentialSignerOpts{ + // Attributes: []bccsp.IdemixAttribute{ + // {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + // {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + // {Type: bccsp.IdemixIntAttribute, Value: 1}, + // {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + // {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + // }, + // }, + // ) + // Expect(err).NotTo(HaveOccurred()) + credential, err = ioutil.ReadFile("./testdata/old/credential.sign") + Expect(err).NotTo(HaveOccurred()) + + // Revocation + raw, err = ioutil.ReadFile("./testdata/old/revocation.sk") + Expect(err).NotTo(HaveOccurred()) + RevocationKey, err = CSP.KeyImport(raw, &bccsp.IdemixRevocationKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // CRI + // cri, err = CSP.Sign( + // RevocationKey, + // nil, + // &bccsp.IdemixCRISignerOpts{}, + // ) + // Expect(err).NotTo(HaveOccurred()) + cri, err = ioutil.ReadFile("./testdata/old/cri.sign") + Expect(err).NotTo(HaveOccurred()) + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CSP.Verify( + RevocationPublicKey, + cri, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Describe("producing an idemix signature with no disclosed attribute", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + // signature, err = CSP.Sign( + // UserKey, + // digest, + // &bccsp.IdemixSignerOpts{ + // Credential: credential, + // Nym: NymKey, + // IssuerPK: IssuerPublicKey, + // Attributes: []bccsp.IdemixAttribute{ + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // }, + // RhIndex: 4, + // Epoch: 0, + // CRI: cri, + // }, + // ) + // Expect(err).NotTo(HaveOccurred()) + signature, err = ioutil.ReadFile("./testdata/old/signature_no_disclosed_attribute.sign") + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with disclosed attributes", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + // signature, err = CSP.Sign( + // UserKey, + // digest, + // &bccsp.IdemixSignerOpts{ + // Credential: credential, + // Nym: NymKey, + // IssuerPK: IssuerPublicKey, + // Attributes: []bccsp.IdemixAttribute{ + // {Type: bccsp.IdemixBytesAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixIntAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // {Type: bccsp.IdemixHiddenAttribute}, + // }, + // RhIndex: 4, + // Epoch: 0, + // CRI: cri, + // }, + // ) + // Expect(err).NotTo(HaveOccurred()) + signature, err = ioutil.ReadFile("./testdata/old/signature_with_disclosed_attribute.sign") + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix nym signature", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + // signature, err = CSP.Sign( + // UserKey, + // digest, + // &bccsp.IdemixNymSignerOpts{ + // Nym: NymKey, + // IssuerPK: IssuerPublicKey, + // }, + // ) + // Expect(err).NotTo(HaveOccurred()) + signature, err = ioutil.ReadFile("./testdata/old/nym_signature.sign") + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + NymPublicKey, + signature, + digest, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + }) +}) diff --git a/v2/bccsp/handlers/cred.go b/v2/bccsp/handlers/cred.go new file mode 100644 index 0000000..740a456 --- /dev/null +++ b/v2/bccsp/handlers/cred.go @@ -0,0 +1,169 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/pkg/errors" +) + +// CredentialRequestSigner produces credential requests +type CredentialRequestSigner struct { + // CredRequest implements the underlying cryptographic algorithms + CredRequest types.CredRequest +} + +func (c *CredentialRequestSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) ([]byte, error) { + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *userSecretKey") + } + credentialRequestSignerOpts, ok := opts.(*bccsp.IdemixCredentialRequestSignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixCredentialRequestSignerOpts") + } + if credentialRequestSignerOpts.IssuerPK == nil { + return nil, errors.New("invalid options, missing issuer public key") + } + issuerPK, ok := credentialRequestSignerOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return nil, errors.New("invalid options, expected IssuerPK as *issuerPublicKey") + } + + return c.CredRequest.Sign(userSecretKey.Sk, issuerPK.pk, credentialRequestSignerOpts.IssuerNonce) +} + +type BlindCredentialRequestSigner struct { + CredRequest types.BlindCredRequest +} + +func (c *BlindCredentialRequestSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) ([]byte, error) { + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *userSecretKey") + } + credentialRequestSignerOpts, ok := opts.(*bccsp.IdemixBlindCredentialRequestSignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixCredentialRequestSignerOpts") + } + if credentialRequestSignerOpts.IssuerPK == nil { + return nil, errors.New("invalid options, missing issuer public key") + } + issuerPK, ok := credentialRequestSignerOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return nil, errors.New("invalid options, expected IssuerPK as *issuerPublicKey") + } + + cred, blinding, err := c.CredRequest.Blind(userSecretKey.Sk, issuerPK.pk, credentialRequestSignerOpts.IssuerNonce) + if err != nil { + return nil, err + } + + credentialRequestSignerOpts.Blinding = blinding + + return cred, err +} + +// CredentialRequestVerifier verifies credential requests +type CredentialRequestVerifier struct { + // CredRequest implements the underlying cryptographic algorithms + CredRequest types.CredRequest +} + +func (c *CredentialRequestVerifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) { + issuerPublicKey, ok := k.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid key, expected *issuerPublicKey") + } + credentialRequestSignerOpts, ok := opts.(*bccsp.IdemixCredentialRequestSignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixCredentialRequestSignerOpts") + } + + err := c.CredRequest.Verify(signature, issuerPublicKey.pk, credentialRequestSignerOpts.IssuerNonce) + if err != nil { + return false, err + } + + return true, nil +} + +type BlindCredentialRequestVerifier struct { + CredRequest types.BlindCredRequest +} + +func (c *BlindCredentialRequestVerifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) { + issuerPublicKey, ok := k.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid key, expected *issuerPublicKey") + } + credentialRequestSignerOpts, ok := opts.(*bccsp.IdemixBlindCredentialRequestSignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixCredentialRequestSignerOpts") + } + + err := c.CredRequest.BlindVerify(signature, issuerPublicKey.pk, credentialRequestSignerOpts.IssuerNonce) + if err != nil { + return false, err + } + + return true, nil +} + +type CredentialSigner struct { + Credential types.Credential +} + +func (s *CredentialSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) { + issuerSecretKey, ok := k.(*issuerSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *issuerSecretKey") + } + credOpts, ok := opts.(*bccsp.IdemixCredentialSignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixCredentialSignerOpts") + } + + signature, err = s.Credential.Sign(issuerSecretKey.sk, digest, credOpts.Attributes) + if err != nil { + return nil, err + } + + return +} + +type CredentialVerifier struct { + Credential types.Credential +} + +func (v *CredentialVerifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) { + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return false, errors.New("invalid key, expected *userSecretKey") + } + credOpts, ok := opts.(*bccsp.IdemixCredentialSignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixCredentialSignerOpts") + } + if credOpts.IssuerPK == nil { + return false, errors.New("invalid options, missing issuer public key") + } + ipk, ok := credOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid issuer public key, expected *issuerPublicKey") + } + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + + err = v.Credential.Verify(userSecretKey.Sk, ipk.pk, signature, credOpts.Attributes) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/v2/bccsp/handlers/cred_test.go b/v2/bccsp/handlers/cred_test.go new file mode 100644 index 0000000..938b27a --- /dev/null +++ b/v2/bccsp/handlers/cred_test.go @@ -0,0 +1,498 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "crypto/rand" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/errors" +) + +var _ = Describe("Credential Request", func() { + + Describe("when creating a credential request", func() { + + var ( + CredentialRequestSigner *handlers.CredentialRequestSigner + fakeCredRequest *mock.CredRequest + ) + + BeforeEach(func() { + fakeCredRequest = &mock.CredRequest{} + CredentialRequestSigner = &handlers.CredentialRequestSigner{CredRequest: fakeCredRequest} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + fakeSignature []byte + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + fakeCredRequest.SignReturns(fakeSignature, nil) + }) + + It("returns no error and a signature", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeCredRequest.SignReturns(nil, errors.New("sign error")) + }) + + It("returns an error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("sign error")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the options are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + nil, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the user secret key is not of type *userSecretKey", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewIssuerPublicKey(nil), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is missing", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialRequestSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is not of type *bccsp.IdemixCredentialRequestSignerOpts", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + &bccsp.IdemixSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialRequestSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the issuer public key is missing", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: nil}, + ) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the issuer public key is not of type *issuerPublicKey", func() { + It("returns error", func() { + signature, err := CredentialRequestSigner.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: handlers.NewUserSecretKey(nil, false)}, + ) + Expect(err).To(MatchError("invalid options, expected IssuerPK as *issuerPublicKey")) + Expect(signature).To(BeNil()) + }) + }) + + }) + }) + + Describe("when verifying a credential request", func() { + + var ( + CredentialRequestVerifier *handlers.CredentialRequestVerifier + IssuerNonce []byte + fakeCredRequest *mock.CredRequest + ) + + BeforeEach(func() { + fakeCredRequest = &mock.CredRequest{} + IssuerNonce = make([]byte, 32) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(32)) + Expect(err).NotTo(HaveOccurred()) + CredentialRequestVerifier = &handlers.CredentialRequestVerifier{CredRequest: fakeCredRequest} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeCredRequest.VerifyReturns(nil) + }) + + It("returns no error and valid signature", func() { + valid, err := CredentialRequestVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeCredRequest.VerifyReturns(errors.New("verify error")) + }) + + It("returns an error", func() { + valid, err := CredentialRequestVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).To(MatchError("verify error")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the issuer public key is nil", func() { + It("returns error", func() { + valid, err := CredentialRequestVerifier.Verify( + nil, + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the issuer public key is not of type *issuerPublicKey", func() { + It("returns error", func() { + valid, err := CredentialRequestVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and nil options are passed", func() { + It("returns error", func() { + valid, err := CredentialRequestVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialRequestSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and non-valid options are passed", func() { + It("returns error", func() { + valid, err := CredentialRequestVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialRequestSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + }) + }) +}) + +var _ = Describe("Credential", func() { + + Describe("when creating a credential", func() { + + var ( + CredentialSigner *handlers.CredentialSigner + fakeCredential *mock.Credential + ) + + BeforeEach(func() { + fakeCredential = &mock.Credential{} + CredentialSigner = &handlers.CredentialSigner{Credential: fakeCredential} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + fakeSignature []byte + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + fakeCredential.SignReturns(fakeSignature, nil) + }) + + It("returns no error and a signature", func() { + signature, err := CredentialSigner.Sign( + handlers.NewIssuerSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialSignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeCredential.SignReturns(nil, errors.New("sign error")) + }) + + It("returns an error", func() { + signature, err := CredentialSigner.Sign( + handlers.NewIssuerSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialSignerOpts{}, + ) + Expect(err).To(MatchError("sign error")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the issuer secret key is nil", func() { + It("returns error", func() { + signature, err := CredentialSigner.Sign( + nil, + nil, + &bccsp.IdemixCredentialSignerOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the user secret key is not of type *issuerSecretKey", func() { + It("returns error", func() { + signature, err := CredentialSigner.Sign( + handlers.NewIssuerPublicKey(nil), + nil, + &bccsp.IdemixCredentialSignerOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the opt is nil", func() { + It("returns error", func() { + signature, err := CredentialSigner.Sign( + handlers.NewIssuerSecretKey(nil, false), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the opt is not of type *IdemixCredentialSignerOpts", func() { + It("returns error", func() { + signature, err := CredentialSigner.Sign( + handlers.NewIssuerSecretKey(nil, false), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + }) + }) + + Describe("when verifying a credential", func() { + + var ( + CredentialVerifier *handlers.CredentialVerifier + fakeCredential *mock.Credential + ) + + BeforeEach(func() { + fakeCredential = &mock.Credential{} + CredentialVerifier = &handlers.CredentialVerifier{Credential: fakeCredential} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeCredential.VerifyReturns(nil) + }) + + It("returns no error and valid signature", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeCredential.VerifyReturns(errors.New("verify error")) + }) + + It("returns an error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("verify error")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + nil, + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the user secret key is not of type *userSecretKey", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the signature is empty", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + nil, + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is empty", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is not of type *IdemixCredentialSignerOpts", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCredentialSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option's issuer public key is empty", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option's issuer public key is not of type *issuerPublicKey", func() { + It("returns error", func() { + valid, err := CredentialVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialSignerOpts{IssuerPK: handlers.NewUserSecretKey(nil, false)}, + ) + Expect(err).To(MatchError("invalid issuer public key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + }) + }) +}) diff --git a/v2/bccsp/handlers/idemix_suite_test.go b/v2/bccsp/handlers/idemix_suite_test.go new file mode 100644 index 0000000..840c85b --- /dev/null +++ b/v2/bccsp/handlers/idemix_suite_test.go @@ -0,0 +1,28 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +//go:generate counterfeiter -o mock/issuer.go -fake-name Issuer . Issuer +//go:generate counterfeiter -o mock/issuer_secret_key.go -fake-name IssuerSecretKey . IssuerSecretKey +//go:generate counterfeiter -o mock/issuer_public_key.go -fake-name IssuerPublicKey . IssuerPublicKey +//go:generate counterfeiter -o mock/user.go -fake-name User . User +//go:generate counterfeiter -o mock/credrequest.go -fake-name CredRequest . CredRequest +//go:generate counterfeiter -o mock/credential.go -fake-name Credential . Credential +//go:generate counterfeiter -o mock/revocation.go -fake-name Revocation . Revocation +//go:generate counterfeiter -o mock/signature_scheme.go -fake-name SignatureScheme . SignatureScheme +//go:generate counterfeiter -o mock/nymsignature_scheme.go -fake-name NymSignatureScheme . NymSignatureScheme + +func TestPlain(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plain Suite") +} diff --git a/v2/bccsp/handlers/issuer.go b/v2/bccsp/handlers/issuer.go new file mode 100644 index 0000000..bf92527 --- /dev/null +++ b/v2/bccsp/handlers/issuer.go @@ -0,0 +1,172 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/pkg/errors" +) + +// issuerSecretKey contains the issuer secret key +// and implements the bccsp.Key interface +type issuerSecretKey struct { + // sk is the idemix reference to the issuer key + sk types.IssuerSecretKey + // exportable if true, sk can be exported via the Bytes function + exportable bool +} + +func NewIssuerSecretKey(sk types.IssuerSecretKey, exportable bool) *issuerSecretKey { + return &issuerSecretKey{sk: sk, exportable: exportable} +} + +func (k *issuerSecretKey) Bytes() ([]byte, error) { + if k.exportable { + return k.sk.Bytes() + } + + return nil, errors.New("not exportable") +} + +func (k *issuerSecretKey) SKI() []byte { + pk, err := k.PublicKey() + if err != nil { + return nil + } + + return pk.SKI() +} + +func (*issuerSecretKey) Symmetric() bool { + return false +} + +func (*issuerSecretKey) Private() bool { + return true +} + +func (k *issuerSecretKey) PublicKey() (bccsp.Key, error) { + return &issuerPublicKey{k.sk.Public()}, nil +} + +// issuerPublicKey contains the issuer public key +// and implements the bccsp.Key interface +type issuerPublicKey struct { + pk types.IssuerPublicKey +} + +func NewIssuerPublicKey(pk types.IssuerPublicKey) *issuerPublicKey { + return &issuerPublicKey{pk} +} + +func (k *issuerPublicKey) Bytes() ([]byte, error) { + return k.pk.Bytes() +} + +func (k *issuerPublicKey) SKI() []byte { + return k.pk.Hash() +} + +func (*issuerPublicKey) Symmetric() bool { + return false +} + +func (*issuerPublicKey) Private() bool { + return false +} + +func (k *issuerPublicKey) PublicKey() (bccsp.Key, error) { + return k, nil +} + +// IssuerKeyGen generates issuer secret keys. +type IssuerKeyGen struct { + // exportable is a flag to allow an issuer secret key to be marked as exportable. + // If a secret key is marked as exportable, its Bytes method will return the key's byte representation. + Exportable bool + // Issuer implements the underlying cryptographic algorithms + Issuer types.Issuer +} + +func (g *IssuerKeyGen) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) { + o, ok := opts.(*bccsp.IdemixIssuerKeyGenOpts) + if !ok { + return nil, errors.New("invalid options, expected *bccsp.IdemixIssuerKeyGenOpts") + } + + // Create a new key pair + key, err := g.Issuer.NewKey(o.AttributeNames) + if err != nil { + return nil, err + } + + return &issuerSecretKey{exportable: g.Exportable, sk: key}, nil +} + +// IssuerPublicKeyImporter imports issuer public keys +type IssuerPublicKeyImporter struct { + // Issuer implements the underlying cryptographic algorithms + Issuer types.Issuer +} + +func (i *IssuerPublicKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + der, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(der) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + o, ok := opts.(*bccsp.IdemixIssuerPublicKeyImportOpts) + if !ok { + return nil, errors.New("invalid options, expected *bccsp.IdemixIssuerPublicKeyImportOpts") + } + + pk, err := i.Issuer.NewPublicKeyFromBytes(raw.([]byte), o.AttributeNames) + if err != nil { + return nil, err + } + + return &issuerPublicKey{pk}, nil +} + +// IssuerKeyImporter imports issuer public keys +type IssuerKeyImporter struct { + // exportable is a flag to allow an issuer secret key to be marked as exportable. + // If a secret key is marked as exportable, its Bytes method will return the key's byte representation. + Exportable bool + // Issuer implements the underlying cryptographic algorithms + Issuer types.Issuer +} + +func (i *IssuerKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + der, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(der) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + o, ok := opts.(*bccsp.IdemixIssuerKeyImportOpts) + if !ok { + return nil, errors.New("invalid options, expected *bccsp.IdemixIssuerKeyImportOpts") + } + + sk, err := i.Issuer.NewKeyFromBytes(raw.([]byte), o.AttributeNames) + if err != nil { + return nil, err + } + + return &issuerSecretKey{ + sk: sk, + exportable: i.Exportable, + }, nil +} diff --git a/v2/bccsp/handlers/issuer_test.go b/v2/bccsp/handlers/issuer_test.go new file mode 100644 index 0000000..3feee85 --- /dev/null +++ b/v2/bccsp/handlers/issuer_test.go @@ -0,0 +1,269 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/errors" +) + +type WrongOpts struct { +} + +func (opts *WrongOpts) Algorithm() string { + return "" +} + +func (opts *WrongOpts) Ephemeral() bool { + return false +} + +var _ = Describe("Issuer", func() { + + Describe("when creating an issuer key-pair", func() { + var ( + IssuerKeyGen *handlers.IssuerKeyGen + + fakeIssuer *mock.Issuer + IssuerSecretKey bccsp.Key + ) + + BeforeEach(func() { + fakeIssuer = &mock.Issuer{} + + IssuerKeyGen = &handlers.IssuerKeyGen{} + IssuerKeyGen.Issuer = fakeIssuer + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + sk bccsp.Key + fakeIssuerSecretKey *mock.IssuerSecretKey + SKI []byte + pkBytes []byte + ) + BeforeEach(func() { + SKI = []byte("a fake SKI") + pkBytes = []byte("a fake public") + + fakeIssuerPublicKey := &mock.IssuerPublicKey{} + fakeIssuerPublicKey.BytesReturns(pkBytes, nil) + fakeIssuerPublicKey.HashReturns(SKI) + + fakeIssuerSecretKey = &mock.IssuerSecretKey{} + fakeIssuerSecretKey.PublicReturns(fakeIssuerPublicKey) + fakeIssuerSecretKey.BytesReturns([]byte("private"), nil) + + fakeIssuer.NewKeyReturns(fakeIssuerSecretKey, nil) + + IssuerSecretKey = handlers.NewIssuerSecretKey(fakeIssuerSecretKey, false) + }) + + AfterEach(func() { + Expect(sk.Private()).To(BeTrue()) + Expect(sk.Symmetric()).To(BeFalse()) + Expect(sk.SKI()).NotTo(BeNil()) + Expect(sk.SKI()).To(BeEquivalentTo(SKI)) + + pk, err := sk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + Expect(pk.Private()).To(BeFalse()) + Expect(pk.Symmetric()).To(BeFalse()) + Expect(pk.SKI()).NotTo(BeNil()) + Expect(pk.SKI()).To(BeEquivalentTo(SKI)) + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo(pkBytes)) + + pk2, err := pk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + Expect(pk).To(BeEquivalentTo(pk2)) + }) + + Context("and the secret key is exportable", func() { + BeforeEach(func() { + IssuerKeyGen.Exportable = true + IssuerSecretKey = handlers.NewIssuerSecretKey(fakeIssuerSecretKey, true) + }) + + It("returns no error and a key", func() { + var err error + sk, err = IssuerKeyGen.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(IssuerSecretKey)) + + raw, err := sk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo([]byte("private"))) + }) + }) + + Context("and the secret key is not exportable", func() { + BeforeEach(func() { + IssuerKeyGen.Exportable = false + IssuerSecretKey = handlers.NewIssuerSecretKey(fakeIssuerSecretKey, false) + }) + + It("returns no error and a key", func() { + sk, err := IssuerKeyGen.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(IssuerSecretKey)) + + raw, err := sk.Bytes() + Expect(err).To(MatchError("not exportable")) + Expect(raw).To(BeNil()) + }) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeIssuer.NewKeyReturns(nil, errors.New("new-key error")) + }) + + It("returns an error", func() { + keyPair, err := IssuerKeyGen.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{}) + Expect(err).To(MatchError("new-key error")) + Expect(keyPair).To(BeNil()) + }) + }) + + Context("and the options are not well formed", func() { + + Context("and the option is nil", func() { + It("returns error", func() { + sk, err := IssuerKeyGen.KeyGen(nil) + Expect(err).To(MatchError("invalid options, expected *bccsp.IdemixIssuerKeyGenOpts")) + Expect(sk).To(BeNil()) + }) + }) + + Context("and the option is not of type *bccsp.IdemixIssuerKeyGenOpts", func() { + It("returns error", func() { + sk, err := IssuerKeyGen.KeyGen(&WrongOpts{}) + Expect(err).To(MatchError("invalid options, expected *bccsp.IdemixIssuerKeyGenOpts")) + Expect(sk).To(BeNil()) + }) + }) + }) + }) + + Describe("when importing an issuer public key", func() { + var ( + IssuerPublicKeyImporter *handlers.IssuerPublicKeyImporter + + fakeIssuer *mock.Issuer + IssuerPublicKey bccsp.Key + ) + + BeforeEach(func() { + fakeIssuer = &mock.Issuer{} + + IssuerPublicKeyImporter = &handlers.IssuerPublicKeyImporter{} + IssuerPublicKeyImporter.Issuer = fakeIssuer + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + pk bccsp.Key + fakeIssuerPublicKey *mock.IssuerPublicKey + SKI []byte + pkBytes []byte + fakeRaw []byte + ) + + BeforeEach(func() { + fakeRaw = []byte("a fake raw") + SKI = []byte("a fake SKI") + pkBytes = []byte("a fake public") + + fakeIssuerPublicKey = &mock.IssuerPublicKey{} + fakeIssuerPublicKey.BytesReturns(pkBytes, nil) + fakeIssuerPublicKey.HashReturns(SKI) + + fakeIssuer.NewPublicKeyFromBytesReturns(fakeIssuerPublicKey, nil) + }) + + Context("and the secret key is exportable", func() { + BeforeEach(func() { + IssuerPublicKey = handlers.NewIssuerPublicKey(fakeIssuerPublicKey) + }) + + It("returns no error and a key", func() { + var err error + pk, err = IssuerPublicKeyImporter.KeyImport(fakeRaw, &bccsp.IdemixIssuerPublicKeyImportOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(pk).To(BeEquivalentTo(IssuerPublicKey)) + + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo(pkBytes)) + + pk, err := pk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + Expect(pk).To(BeEquivalentTo(IssuerPublicKey)) + }) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeIssuer.NewPublicKeyFromBytesReturns(nil, errors.New("new-key error")) + }) + + It("returns an error", func() { + pk, err := IssuerPublicKeyImporter.KeyImport([]byte{1, 2, 3}, &bccsp.IdemixIssuerPublicKeyImportOpts{}) + Expect(err).To(MatchError("new-key error")) + Expect(pk).To(BeNil()) + }) + }) + + Context("and the arguments are not well formed", func() { + + Context("and the raw is nil", func() { + It("returns error", func() { + pk, err := IssuerPublicKeyImporter.KeyImport(nil, &bccsp.IdemixIssuerPublicKeyImportOpts{}) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(pk).To(BeNil()) + }) + }) + + Context("and the raw is empty", func() { + It("returns error", func() { + pk, err := IssuerPublicKeyImporter.KeyImport([]byte{}, &bccsp.IdemixIssuerPublicKeyImportOpts{}) + Expect(err).To(MatchError("invalid raw, it must not be nil")) + Expect(pk).To(BeNil()) + }) + }) + + Context("and the option is nil", func() { + It("returns error", func() { + pk, err := IssuerPublicKeyImporter.KeyImport([]byte{1, 2, 3}, nil) + Expect(err).To(MatchError("invalid options, expected *bccsp.IdemixIssuerPublicKeyImportOpts")) + Expect(pk).To(BeNil()) + }) + }) + + Context("and the option is not of type *bccsp.IdemixIssuerPublicKeyImportOpts", func() { + It("returns error", func() { + pk, err := IssuerPublicKeyImporter.KeyImport([]byte{1, 2, 3}, &bccsp.IdemixNymPublicKeyImportOpts{}) + Expect(err).To(MatchError("invalid options, expected *bccsp.IdemixIssuerPublicKeyImportOpts")) + Expect(pk).To(BeNil()) + }) + }) + }) + + }) +}) diff --git a/v2/bccsp/handlers/nym.go b/v2/bccsp/handlers/nym.go new file mode 100644 index 0000000..5ea64a1 --- /dev/null +++ b/v2/bccsp/handlers/nym.go @@ -0,0 +1,205 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "crypto/sha256" + + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// NymSecretKey contains the nym secret key +type NymSecretKey struct { + // SKI of this key + Ski []byte + // Sk is the idemix reference to the nym secret + Sk *math.Zr + // Pk is the idemix reference to the nym public part + Pk *math.G1 + // Exportable if true, sk can be exported via the Bytes function + Exportable bool + + Translator idemix.Translator +} + +func computeSKI(serialise func() []byte) []byte { + raw := serialise() + + hash := sha256.New() + hash.Write(raw) + return hash.Sum(nil) + +} + +func NewNymSecretKey(sk *math.Zr, pk *math.G1, translator idemix.Translator, exportable bool) (*NymSecretKey, error) { + ski := computeSKI(sk.Bytes) + return &NymSecretKey{Ski: ski, Sk: sk, Pk: pk, Exportable: exportable, Translator: translator}, nil +} + +func (k *NymSecretKey) Bytes() ([]byte, error) { + if k.Exportable { + return k.Sk.Bytes(), nil + } + + return nil, errors.New("not supported") +} + +func (k *NymSecretKey) SKI() []byte { + c := make([]byte, len(k.Ski)) + copy(c, k.Ski) + return c +} + +func (*NymSecretKey) Symmetric() bool { + return false +} + +func (*NymSecretKey) Private() bool { + return true +} + +func (k *NymSecretKey) PublicKey() (bccsp.Key, error) { + ski := computeSKI(k.Pk.Bytes) + return &nymPublicKey{ski: ski, pk: k.Pk, translator: k.Translator}, nil +} + +type nymPublicKey struct { + // SKI of this key + ski []byte + // pk is the idemix reference to the nym public part + pk *math.G1 + + translator idemix.Translator +} + +func NewNymPublicKey(pk *math.G1, translator idemix.Translator) *nymPublicKey { + return &nymPublicKey{pk: pk, translator: translator} +} + +func (k *nymPublicKey) Bytes() ([]byte, error) { + ecp := k.translator.G1ToProto(k.pk) + return append(ecp.X, ecp.Y...), nil +} + +func (k *nymPublicKey) SKI() []byte { + return computeSKI(k.pk.Bytes) +} + +func (*nymPublicKey) Symmetric() bool { + return false +} + +func (*nymPublicKey) Private() bool { + return false +} + +func (k *nymPublicKey) PublicKey() (bccsp.Key, error) { + return k, nil +} + +// NymKeyDerivation derives nyms +type NymKeyDerivation struct { + // Exportable is a flag to allow an issuer secret key to be marked as Exportable. + // If a secret key is marked as Exportable, its Bytes method will return the key's byte representation. + Exportable bool + // User implements the underlying cryptographic algorithms + User types.User + + Translator idemix.Translator +} + +func (kd *NymKeyDerivation) KeyDeriv(k bccsp.Key, opts bccsp.KeyDerivOpts) (dk bccsp.Key, err error) { + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *userSecretKey") + } + nymKeyDerivationOpts, ok := opts.(*bccsp.IdemixNymKeyDerivationOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixNymKeyDerivationOpts") + } + if nymKeyDerivationOpts.IssuerPK == nil { + return nil, errors.New("invalid options, missing issuer public key") + } + issuerPK, ok := nymKeyDerivationOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return nil, errors.New("invalid options, expected IssuerPK as *issuerPublicKey") + } + + Nym, RandNym, err := kd.User.MakeNym(userSecretKey.Sk, issuerPK.pk) + if err != nil { + return nil, err + } + + return NewNymSecretKey(RandNym, Nym, kd.Translator, kd.Exportable) +} + +// NymPublicKeyImporter imports nym public keys +type NymPublicKeyImporter struct { + // User implements the underlying cryptographic algorithms + User types.User + + Translator idemix.Translator +} + +func (i *NymPublicKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + bytes, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(bytes) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + pk, err := i.User.NewPublicNymFromBytes(bytes) + if err != nil { + // A Nym public key is just a group element. There are 2 main ways of serialising + // an uncompressed point: either the two coordinates, or the prefix `0x04` and the + // two coordinates. Typically these serialisation issues are handled with a + // `translator` object which gets bytes, understands the serialisation and produces + // a group element. This code does not have that, however. As a consequence, we + // handle the issue by prefixing the `0x04` byte and re-attempting deserialisation + // in case it first failed. Issue https://github.com/IBM/idemix/issues/42 has + // been created to fix this properly by adding the translator. + pk, err = i.User.NewPublicNymFromBytes(append([]byte{04}, bytes...)) + if err != nil { + return nil, err + } + } + + return &nymPublicKey{pk: pk, translator: i.Translator}, nil +} + +// NymKeyImporter imports nym public keys +type NymKeyImporter struct { + Exportable bool + // User implements the underlying cryptographic algorithms + User types.User + + Translator idemix.Translator +} + +func (i *NymKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + bytes, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(bytes) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + pk, sk, err := i.User.NewNymFromBytes(bytes) + if err != nil { + return nil, err + } + + return NewNymSecretKey(sk, pk, i.Translator, i.Exportable) +} diff --git a/v2/bccsp/handlers/nymsigner.go b/v2/bccsp/handlers/nymsigner.go new file mode 100644 index 0000000..80b0062 --- /dev/null +++ b/v2/bccsp/handlers/nymsigner.go @@ -0,0 +1,142 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "fmt" + + "github.com/IBM/idemix/bccsp/types" + "github.com/pkg/errors" +) + +type NymSigner struct { + NymSignatureScheme types.NymSignatureScheme + SmartcardNymSignatureScheme types.SmartcardNymSignatureScheme +} + +func (s *NymSigner) Sign(k types.Key, digest []byte, opts types.SignerOpts) ([]byte, error) { + signerOpts, ok := opts.(*types.IdemixNymSignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixNymSignerOpts") + } + + // Issuer public key + if signerOpts.IssuerPK == nil { + return nil, errors.New("invalid options, missing issuer public key") + } + ipk, ok := signerOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return nil, errors.New("invalid issuer public key, expected *issuerPublicKey") + } + + // handle the smartcard case + if signerOpts.IsSmartcard { + if s.SmartcardNymSignatureScheme == nil { + return nil, fmt.Errorf("smartcard mode is unsupported") + } + + if signerOpts.Smartcard == nil { + return nil, fmt.Errorf("no s/w smartcard supplied in opts") + } + + sigma, nym, rNym, err := s.SmartcardNymSignatureScheme.Sign(signerOpts.Smartcard, ipk.pk, digest) + if err != nil { + return nil, err + } + + signerOpts.NymG1 = nym + signerOpts.RNym = rNym + + return sigma, nil + } + + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *userSecretKey") + } + + // Nym + if signerOpts.Nym == nil { + return nil, errors.New("invalid options, missing nym key") + } + nymSk, ok := signerOpts.Nym.(*NymSecretKey) + if !ok { + return nil, errors.New("invalid nym key, expected *nymSecretKey") + } + + sigma, err := s.NymSignatureScheme.Sign( + userSecretKey.Sk, + nymSk.Pk, nymSk.Sk, + ipk.pk, + digest) + if err != nil { + return nil, err + } + + return sigma, nil +} + +type NymVerifier struct { + NymSignatureScheme types.NymSignatureScheme + SmartcardNymSignatureScheme types.SmartcardNymSignatureScheme +} + +func (v *NymVerifier) Verify(k types.Key, signature, digest []byte, opts types.SignerOpts) (bool, error) { + nymPublicKey, ok := k.(*nymPublicKey) + if !ok { + return false, errors.New("invalid key, expected *nymPublicKey") + } + + signerOpts, ok := opts.(*types.IdemixNymSignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixNymSignerOpts") + } + + if signerOpts.IssuerPK == nil { + return false, errors.New("invalid options, missing issuer public key") + } + ipk, ok := signerOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid issuer public key, expected *issuerPublicKey") + } + + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + + // handle the smartcard case + if signerOpts.IsSmartcard { + if v.SmartcardNymSignatureScheme == nil { + return false, fmt.Errorf("smartcard mode is unsupported") + } + if signerOpts.NymEid == nil { + return false, fmt.Errorf("nym eid missing") + } + + err := v.SmartcardNymSignatureScheme.Verify( + ipk.pk, + signerOpts.NymEid, + signature, + digest) + if err != nil { + return false, err + } + + return true, nil + } + + err := v.NymSignatureScheme.Verify( + ipk.pk, + nymPublicKey.pk, + signature, + digest, + signerOpts.SKIndex) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/v2/bccsp/handlers/nymsigner_test.go b/v2/bccsp/handlers/nymsigner_test.go new file mode 100644 index 0000000..ecaf73a --- /dev/null +++ b/v2/bccsp/handlers/nymsigner_test.go @@ -0,0 +1,345 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "errors" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Nym Signature", func() { + + Describe("when creating a signature", func() { + + var ( + NymSigner *handlers.NymSigner + fakeSignatureScheme *mock.NymSignatureScheme + nymSK bccsp.Key + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.NymSignatureScheme{} + NymSigner = &handlers.NymSigner{NymSignatureScheme: fakeSignatureScheme} + + var err error + sk := math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + nymSK, err = handlers.NewNymSecretKey(sk, nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}, false) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + fakeSignature []byte + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + fakeSignatureScheme.SignReturns(fakeSignature, nil) + }) + + It("returns no error and a signature", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeSignatureScheme.SignReturns(nil, errors.New("sign error")) + }) + + It("returns an error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("sign error")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + nil, + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the user secret key is not of type *userSecretKey", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewIssuerPublicKey(nil), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is nil", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixNymSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is not of type *IdemixNymSignerOpts", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixNymSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the nym is nil", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid options, missing nym key")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the nym is not of type *nymSecretKey", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: handlers.NewIssuerPublicKey(nil), + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid nym key, expected *nymSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the IssuerPk is nil", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + }, + ) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the IssuerPk is not of type *issuerPublicKey", func() { + It("returns error", func() { + signature, err := NymSigner.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewUserSecretKey(nil, false), + }, + ) + Expect(err).To(MatchError("invalid issuer public key, expected *issuerPublicKey")) + Expect(signature).To(BeNil()) + }) + }) + }) + }) + + Describe("when verifying a signature", func() { + + var ( + NymVerifier *handlers.NymVerifier + fakeSignatureScheme *mock.NymSignatureScheme + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.NymSignatureScheme{} + NymVerifier = &handlers.NymVerifier{NymSignatureScheme: fakeSignatureScheme} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeSignatureScheme.VerifyReturns(nil) + }) + + It("returns no error and valid signature", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeSignatureScheme.VerifyReturns(errors.New("verify error")) + }) + + It("returns an error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{ + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("verify error")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the nym public key is nil", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + nil, + []byte("fake signature"), + nil, + &bccsp.IdemixNymSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *nymPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the nym public key is not of type *nymPublicKey", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixNymSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *nymPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the signature is empty", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + nil, + []byte("a digest"), + &bccsp.IdemixNymSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is empty", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("a signature"), + []byte("a digest"), + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixNymSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is not of type *IdemixNymSignerOpts", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixCredentialRequestSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixNymSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option's issuer public key is empty", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("fake signature"), + nil, + &bccsp.IdemixNymSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option's issuer public key is not of type *issuerPublicKey", func() { + It("returns error", func() { + valid, err := NymVerifier.Verify( + handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + []byte("fake signature"), + nil, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: handlers.NewNymPublicKey(nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}), + }, + ) + Expect(err).To(MatchError("invalid issuer public key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + }) + }) +}) diff --git a/v2/bccsp/handlers/revocation.go b/v2/bccsp/handlers/revocation.go new file mode 100644 index 0000000..ad75f2a --- /dev/null +++ b/v2/bccsp/handlers/revocation.go @@ -0,0 +1,250 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "fmt" + "reflect" + + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/pkg/errors" +) + +// revocationSecretKey contains the revocation secret key +// and implements the bccsp.Key interface +type revocationSecretKey struct { + // sk is the idemix reference to the revocation key + privKey *ecdsa.PrivateKey + // exportable if true, sk can be exported via the Bytes function + exportable bool +} + +func NewRevocationSecretKey(sk *ecdsa.PrivateKey, exportable bool) *revocationSecretKey { + return &revocationSecretKey{privKey: sk, exportable: exportable} +} + +// Bytes converts this key to its byte representation, +// if this operation is allowed. +func (k *revocationSecretKey) Bytes() ([]byte, error) { + if k.exportable { + return k.privKey.D.Bytes(), nil + } + + return nil, errors.New("not exportable") +} + +// SKI returns the subject key identifier of this key. +func (k *revocationSecretKey) SKI() []byte { + // Marshall the public key + raw := elliptic.Marshal(k.privKey.Curve, k.privKey.PublicKey.X, k.privKey.PublicKey.Y) + + // Hash it + hash := sha256.New() + hash.Write(raw) + return hash.Sum(nil) +} + +// Symmetric returns true if this key is a symmetric key, +// false if this key is asymmetric +func (k *revocationSecretKey) Symmetric() bool { + return false +} + +// Private returns true if this key is a private key, +// false otherwise. +func (k *revocationSecretKey) Private() bool { + return true +} + +// PublicKey returns the corresponding public key part of an asymmetric public/private key pair. +// This method returns an error in symmetric key schemes. +func (k *revocationSecretKey) PublicKey() (bccsp.Key, error) { + return &revocationPublicKey{&k.privKey.PublicKey}, nil +} + +type revocationPublicKey struct { + pubKey *ecdsa.PublicKey +} + +func NewRevocationPublicKey(pubKey *ecdsa.PublicKey) *revocationPublicKey { + return &revocationPublicKey{pubKey: pubKey} +} + +// Bytes converts this key to its byte representation, +// if this operation is allowed. +func (k *revocationPublicKey) Bytes() (raw []byte, err error) { + raw, err = x509.MarshalPKIXPublicKey(k.pubKey) + if err != nil { + return nil, fmt.Errorf("Failed marshalling key [%s]", err) + } + return +} + +// SKI returns the subject key identifier of this key. +func (k *revocationPublicKey) SKI() []byte { + // Marshall the public key + raw := elliptic.Marshal(k.pubKey.Curve, k.pubKey.X, k.pubKey.Y) + + // Hash it + hash := sha256.New() + hash.Write(raw) + return hash.Sum(nil) +} + +// Symmetric returns true if this key is a symmetric key, +// false if this key is asymmetric +func (k *revocationPublicKey) Symmetric() bool { + return false +} + +// Private returns true if this key is a private key, +// false otherwise. +func (k *revocationPublicKey) Private() bool { + return false +} + +// PublicKey returns the corresponding public key part of an asymmetric public/private key pair. +// This method returns an error in symmetric key schemes. +func (k *revocationPublicKey) PublicKey() (bccsp.Key, error) { + return k, nil +} + +// RevocationKeyGen generates revocation secret keys. +type RevocationKeyGen struct { + // exportable is a flag to allow an revocation secret key to be marked as exportable. + // If a secret key is marked as exportable, its Bytes method will return the key's byte representation. + Exportable bool + // Revocation implements the underlying cryptographic algorithms + Revocation types.Revocation +} + +func (g *RevocationKeyGen) KeyGen(opts bccsp.KeyGenOpts) (bccsp.Key, error) { + // Create a new key pair + key, err := g.Revocation.NewKey() + if err != nil { + return nil, err + } + + return &revocationSecretKey{exportable: g.Exportable, privKey: key}, nil +} + +// RevocationPublicKeyImporter imports revocation public key +type RevocationPublicKeyImporter struct { +} + +func (i *RevocationPublicKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + der, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(der) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + blockPub, _ := pem.Decode(raw.([]byte)) + if blockPub == nil { + return nil, errors.New("Failed to decode revocation ECDSA public key") + } + revocationPk, err := x509.ParsePKIXPublicKey(blockPub.Bytes) + if err != nil { + return nil, errors.Wrap(err, "Failed to parse revocation ECDSA public key bytes") + } + ecdsaPublicKey, isECDSA := revocationPk.(*ecdsa.PublicKey) + if !isECDSA { + return nil, errors.Errorf("key is of type %v, not of type ECDSA", reflect.TypeOf(revocationPk)) + } + + return &revocationPublicKey{ecdsaPublicKey}, nil +} + +// RevocationKeyImporter imports revocation key +type RevocationKeyImporter struct { + // exportable is a flag to allow an revocation secret key to be marked as exportable. + // If a secret key is marked as exportable, its Bytes method will return the key's byte representation. + Exportable bool + // Revocation implements the underlying cryptographic algorithms + Revocation types.Revocation +} + +func (i *RevocationKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + der, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(der) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + key, err := i.Revocation.NewKeyFromBytes(raw.([]byte)) + if err != nil { + return nil, err + } + + return &revocationSecretKey{ + privKey: key, + exportable: i.Exportable, + }, nil +} + +type CriSigner struct { + Revocation types.Revocation +} + +func (s *CriSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) ([]byte, error) { + revocationSecretKey, ok := k.(*revocationSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *revocationSecretKey") + } + criOpts, ok := opts.(*bccsp.IdemixCRISignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixCRISignerOpts") + } + + return s.Revocation.Sign( + revocationSecretKey.privKey, + criOpts.UnrevokedHandles, + criOpts.Epoch, + criOpts.RevocationAlgorithm, + ) +} + +type CriVerifier struct { + Revocation types.Revocation +} + +func (v *CriVerifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) { + revocationPublicKey, ok := k.(*revocationPublicKey) + if !ok { + return false, errors.New("invalid key, expected *revocationPublicKey") + } + criOpts, ok := opts.(*bccsp.IdemixCRISignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixCRISignerOpts") + } + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + + err := v.Revocation.Verify( + revocationPublicKey.pubKey, + signature, + criOpts.Epoch, + criOpts.RevocationAlgorithm, + ) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/v2/bccsp/handlers/revocation_test.go b/v2/bccsp/handlers/revocation_test.go new file mode 100644 index 0000000..7007489 --- /dev/null +++ b/v2/bccsp/handlers/revocation_test.go @@ -0,0 +1,429 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "math/big" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/errors" +) + +var _ = Describe("Revocation", func() { + + Describe("when creating a revocation key-pair", func() { + var ( + RevocationKeyGen *handlers.RevocationKeyGen + + fakeRevocation *mock.Revocation + fakeRevocationSecretKey bccsp.Key + ) + + BeforeEach(func() { + fakeRevocation = &mock.Revocation{} + + RevocationKeyGen = &handlers.RevocationKeyGen{} + RevocationKeyGen.Revocation = fakeRevocation + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + sk bccsp.Key + idemixRevocationKey *ecdsa.PrivateKey + SKI []byte + pkBytes []byte + ) + BeforeEach(func() { + idemixRevocationKey = &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: elliptic.P256().Params().Gx, + Y: elliptic.P256().Params().Gy, + }, + D: big.NewInt(1)} + + raw := elliptic.Marshal(idemixRevocationKey.Curve, idemixRevocationKey.PublicKey.X, idemixRevocationKey.PublicKey.Y) + hash := sha256.New() + hash.Write(raw) + SKI = hash.Sum(nil) + + var err error + pkBytes, err = x509.MarshalPKIXPublicKey(&idemixRevocationKey.PublicKey) + Expect(err).NotTo(HaveOccurred()) + + fakeRevocation.NewKeyReturns(idemixRevocationKey, nil) + + fakeRevocationSecretKey = handlers.NewRevocationSecretKey(idemixRevocationKey, false) + }) + + AfterEach(func() { + Expect(sk.Private()).To(BeTrue()) + Expect(sk.Symmetric()).To(BeFalse()) + Expect(sk.SKI()).NotTo(BeNil()) + Expect(sk.SKI()).To(BeEquivalentTo(SKI)) + + pk, err := sk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + Expect(pk.Private()).To(BeFalse()) + Expect(pk.Symmetric()).To(BeFalse()) + Expect(pk.SKI()).NotTo(BeNil()) + Expect(pk.SKI()).To(BeEquivalentTo(SKI)) + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo(pkBytes)) + + pk2, err := pk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + Expect(pk).To(BeEquivalentTo(pk2)) + }) + + Context("and the secret key is exportable", func() { + BeforeEach(func() { + RevocationKeyGen.Exportable = true + fakeRevocationSecretKey = handlers.NewRevocationSecretKey(idemixRevocationKey, true) + }) + + It("returns no error and a key", func() { + var err error + sk, err = RevocationKeyGen.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(fakeRevocationSecretKey)) + + raw, err := sk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo(idemixRevocationKey.D.Bytes())) + }) + }) + + Context("and the secret key is not exportable", func() { + BeforeEach(func() { + RevocationKeyGen.Exportable = false + fakeRevocationSecretKey = handlers.NewRevocationSecretKey(idemixRevocationKey, false) + }) + + It("returns no error and a key", func() { + sk, err := RevocationKeyGen.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(fakeRevocationSecretKey)) + + raw, err := sk.Bytes() + Expect(err).To(MatchError("not exportable")) + Expect(raw).To(BeNil()) + }) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeRevocation.NewKeyReturns(nil, errors.New("new-key error")) + }) + + It("returns an error", func() { + keyPair, err := RevocationKeyGen.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{}) + Expect(err).To(MatchError("new-key error")) + Expect(keyPair).To(BeNil()) + }) + }) + + }) + + Context("when importing a revocation public key", func() { + var ( + RevocationPublicKeyImporter *handlers.RevocationPublicKeyImporter + ) + + BeforeEach(func() { + RevocationPublicKeyImporter = &handlers.RevocationPublicKeyImporter{} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + raw []byte + pemBytes []byte + ) + + BeforeEach(func() { + key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + Expect(err).NotTo(HaveOccurred()) + + raw, err = x509.MarshalPKIXPublicKey(key.Public()) + Expect(err).NotTo(HaveOccurred()) + + pemBytes = pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + Bytes: raw, + }, + ) + }) + + It("import is successful", func() { + k, err := RevocationPublicKeyImporter.KeyImport(pemBytes, nil) + Expect(err).NotTo(HaveOccurred()) + + bytes, err := k.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(bytes).To(BeEquivalentTo(raw)) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + + It("returns an error on nil raw", func() { + k, err := RevocationPublicKeyImporter.KeyImport(nil, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error on empty raw", func() { + k, err := RevocationPublicKeyImporter.KeyImport([]byte{}, nil) + Expect(err).To(MatchError("invalid raw, it must not be nil")) + Expect(k).To(BeNil()) + }) + + It("returns an error on invalid raw", func() { + k, err := RevocationPublicKeyImporter.KeyImport(RevocationPublicKeyImporter, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error", func() { + k, err := RevocationPublicKeyImporter.KeyImport([]byte("fake-raw"), nil) + Expect(err).To(MatchError("Failed to decode revocation ECDSA public key")) + Expect(k).To(BeNil()) + }) + + }) + + }) + +}) + +var _ = Describe("CRI", func() { + + Describe("when creating a CRI", func() { + + var ( + CriSigner *handlers.CriSigner + fakeRevocation *mock.Revocation + ) + + BeforeEach(func() { + fakeRevocation = &mock.Revocation{} + CriSigner = &handlers.CriSigner{Revocation: fakeRevocation} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + fakeSignature []byte + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + fakeRevocation.SignReturns(fakeSignature, nil) + }) + + It("returns no error and a signature", func() { + signature, err := CriSigner.Sign( + handlers.NewRevocationSecretKey(nil, false), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeRevocation.SignReturns(nil, errors.New("sign error")) + }) + + It("returns an error", func() { + signature, err := CriSigner.Sign( + handlers.NewRevocationSecretKey(nil, false), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("sign error")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the revocation secret key is nil", func() { + It("returns error", func() { + signature, err := CriSigner.Sign( + nil, + nil, + nil, + ) + Expect(err).To(MatchError("invalid key, expected *revocationSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the revocation secret key is not of type *revocationSecretKey", func() { + It("returns error", func() { + signature, err := CriSigner.Sign( + handlers.NewIssuerPublicKey(nil), + nil, + nil, + ) + Expect(err).To(MatchError("invalid key, expected *revocationSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + }) + + It("returns an error", func() { + signature, err := CriSigner.Sign( + handlers.NewRevocationSecretKey(nil, false), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCRISignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + }) + }) + + Describe("when verifying a CRI", func() { + + var ( + CriVerifier *handlers.CriVerifier + fakeRevocation *mock.Revocation + ) + + BeforeEach(func() { + fakeRevocation = &mock.Revocation{} + CriVerifier = &handlers.CriVerifier{Revocation: fakeRevocation} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeRevocation.VerifyReturns(nil) + }) + + It("returns no error and valid signature", func() { + valid, err := CriVerifier.Verify( + handlers.NewRevocationPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeRevocation.VerifyReturns(errors.New("verify error")) + }) + + It("returns an error", func() { + valid, err := CriVerifier.Verify( + handlers.NewRevocationPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("verify error")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + valid, err := CriVerifier.Verify( + nil, + []byte("fake signature"), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *revocationPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the user secret key is not of type *revocationPublicKey", func() { + It("returns error", func() { + valid, err := CriVerifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *revocationPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the signature is empty", func() { + It("returns error", func() { + valid, err := CriVerifier.Verify( + handlers.NewRevocationPublicKey(nil), + nil, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is empty", func() { + It("returns error", func() { + valid, err := CriVerifier.Verify( + handlers.NewRevocationPublicKey(nil), + []byte("fake signature"), + nil, + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCRISignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is not of type *IdemixCRISignerOpts", func() { + It("returns error", func() { + valid, err := CriVerifier.Verify( + handlers.NewRevocationPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixCredentialRequestSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixCRISignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + }) + }) +}) diff --git a/v2/bccsp/handlers/signer.go b/v2/bccsp/handlers/signer.go new file mode 100644 index 0000000..8b21f85 --- /dev/null +++ b/v2/bccsp/handlers/signer.go @@ -0,0 +1,177 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "crypto/ecdsa" + + "github.com/IBM/idemix/bccsp/types" + "github.com/pkg/errors" +) + +type Signer struct { + SignatureScheme types.SignatureScheme +} + +func (s *Signer) Sign(k types.Key, digest []byte, opts types.SignerOpts) ([]byte, error) { + userSecretKey, ok := k.(*UserSecretKey) + if !ok { + return nil, errors.New("invalid key, expected *userSecretKey") + } + + signerOpts, ok := opts.(*types.IdemixSignerOpts) + if !ok { + return nil, errors.New("invalid options, expected *IdemixSignerOpts") + } + + // Issuer public key + if signerOpts.IssuerPK == nil { + return nil, errors.New("invalid options, missing issuer public key") + } + ipk, ok := signerOpts.IssuerPK.(*issuerPublicKey) + if !ok { + return nil, errors.New("invalid issuer public key, expected *issuerPublicKey") + } + + // Nym + if signerOpts.Nym == nil { + return nil, errors.New("invalid options, missing nym key") + } + nymSk, ok := signerOpts.Nym.(*NymSecretKey) + if !ok { + return nil, errors.New("invalid nym key, expected *nymSecretKey") + } + + sigma, meta, err := s.SignatureScheme.Sign( + signerOpts.Credential, + userSecretKey.Sk, + nymSk.Pk, + nymSk.Sk, + ipk.pk, + signerOpts.Attributes, + digest, + signerOpts.RhIndex, + signerOpts.EidIndex, + signerOpts.CRI, + signerOpts.SigType, + signerOpts.Metadata, + ) + if err != nil { + return nil, err + } + + signerOpts.Metadata = meta + + return sigma, nil +} + +type Verifier struct { + SignatureScheme types.SignatureScheme +} + +func (v *Verifier) AuditNymEid(k types.Key, signature, digest []byte, opts types.SignerOpts) (bool, error) { + issuerPublicKey, ok := k.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid key, expected *issuerPublicKey") + } + + signerOpts, ok := opts.(*types.EidNymAuditOpts) + if !ok { + return false, errors.New("invalid options, expected *EidNymAuditOpts") + } + + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + + err := v.SignatureScheme.AuditNymEid( + issuerPublicKey.pk, + signerOpts.EidIndex, + signerOpts.SKIndex, + signature, + signerOpts.EnrollmentID, + signerOpts.RNymEid, + signerOpts.AuditVerificationType, + ) + if err != nil { + return false, err + } + + return true, nil +} + +func (v *Verifier) AuditNymRh(k types.Key, signature, digest []byte, opts types.SignerOpts) (bool, error) { + issuerPublicKey, ok := k.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid key, expected *issuerPublicKey") + } + + signerOpts, ok := opts.(*types.RhNymAuditOpts) + if !ok { + return false, errors.New("invalid options, expected *RhNymAuditOpts") + } + + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + + err := v.SignatureScheme.AuditNymRh( + issuerPublicKey.pk, + signerOpts.RhIndex, + signerOpts.SKIndex, + signature, + signerOpts.RevocationHandle, + signerOpts.RNymRh, + signerOpts.AuditVerificationType, + ) + if err != nil { + return false, err + } + + return true, nil +} + +func (v *Verifier) Verify(k types.Key, signature, digest []byte, opts types.SignerOpts) (bool, error) { + issuerPublicKey, ok := k.(*issuerPublicKey) + if !ok { + return false, errors.New("invalid key, expected *issuerPublicKey") + } + + signerOpts, ok := opts.(*types.IdemixSignerOpts) + if !ok { + return false, errors.New("invalid options, expected *IdemixSignerOpts") + } + + var rPK *ecdsa.PublicKey + if signerOpts.RevocationPublicKey != nil { + revocationPK, ok := signerOpts.RevocationPublicKey.(*revocationPublicKey) + if !ok { + return false, errors.New("invalid options, expected *revocationPublicKey") + } + rPK = revocationPK.pubKey + } + + if len(signature) == 0 { + return false, errors.New("invalid signature, it must not be empty") + } + err := v.SignatureScheme.Verify( + issuerPublicKey.pk, + signature, + digest, + signerOpts.Attributes, + signerOpts.RhIndex, + signerOpts.EidIndex, + signerOpts.SKIndex, + rPK, + signerOpts.Epoch, + signerOpts.VerificationType, + signerOpts.Metadata, + ) + if err != nil { + return false, err + } + return true, nil +} diff --git a/v2/bccsp/handlers/signer_test.go b/v2/bccsp/handlers/signer_test.go new file mode 100644 index 0000000..d5b89c3 --- /dev/null +++ b/v2/bccsp/handlers/signer_test.go @@ -0,0 +1,538 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "errors" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Signature", func() { + + Describe("when creating a signature", func() { + + var ( + Signer *handlers.Signer + fakeSignatureScheme *mock.SignatureScheme + nymSK bccsp.Key + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.SignatureScheme{} + Signer = &handlers.Signer{SignatureScheme: fakeSignatureScheme} + + var err error + sk := math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + nymSK, err = handlers.NewNymSecretKey(sk, nil, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}, false) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + fakeSignature []byte + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + fakeSignatureScheme.SignReturns(fakeSignature, nil, nil) + }) + + It("returns no error and a signature", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + + }) + }) + + Context("and the underlying cryptographic algorithm succeed and returns metadata", func() { + var ( + fakeSignature []byte + randomRandomness *math.Zr + ) + BeforeEach(func() { + fakeSignature = []byte("fake signature") + randomRandomness = math.Curves[math.FP256BN_AMCL].NewZrFromInt(35) + fakeSignatureScheme.SignReturns(fakeSignature, &bccsp.IdemixSignerMetadata{EidNymAuditData: &bccsp.AttrNymAuditData{Rand: randomRandomness}}, nil) + }) + + It("returns no error and a signature", func() { + opts := &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + } + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + opts, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).To(BeEquivalentTo(fakeSignature)) + Expect(opts.Metadata).To(BeEquivalentTo(&bccsp.IdemixSignerMetadata{EidNymAuditData: &bccsp.AttrNymAuditData{Rand: randomRandomness}})) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeSignatureScheme.SignReturns(nil, nil, errors.New("sign error")) + }) + + It("returns an error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("sign error")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + signature, err := Signer.Sign( + nil, + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the user secret key is not of type *userSecretKey", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewIssuerPublicKey(nil), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is nil", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the option is not of type *IdemixSignerOpts", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the nym is nil", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid options, missing nym key")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the nym is not of type *nymSecretKey", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: handlers.NewIssuerPublicKey(nil), + IssuerPK: handlers.NewIssuerPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid nym key, expected *nymSecretKey")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the IssuerPk is nil", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + }, + ) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(signature).To(BeNil()) + }) + }) + + Context("and the IssuerPk is not of type *issuerPublicKey", func() { + It("returns error", func() { + signature, err := Signer.Sign( + handlers.NewUserSecretKey(nil, false), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + Nym: nymSK, + IssuerPK: handlers.NewUserSecretKey(nil, false), + }, + ) + Expect(err).To(MatchError("invalid issuer public key, expected *issuerPublicKey")) + Expect(signature).To(BeNil()) + }) + }) + }) + }) + + Describe("when verifying a signature", func() { + + var ( + Verifier *handlers.Verifier + fakeSignatureScheme *mock.SignatureScheme + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.SignatureScheme{} + Verifier = &handlers.Verifier{SignatureScheme: fakeSignatureScheme} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeSignatureScheme.VerifyReturns(nil) + }) + + It("returns no error and valid signature", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: handlers.NewRevocationPublicKey(nil), + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeSignatureScheme.VerifyReturns(errors.New("verify error")) + }) + + It("returns an error", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: handlers.NewRevocationPublicKey(nil), + }, + ) + Expect(err).To(MatchError("verify error")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the parameters are not well formed", func() { + + Context("and the issuer public key is nil", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + nil, + []byte("fake signature"), + nil, + &bccsp.IdemixSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the issuer public key is not of type *issuerPublicKey", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + handlers.NewUserSecretKey(nil, false), + []byte("fake signature"), + nil, + &bccsp.IdemixSignerOpts{IssuerPK: handlers.NewIssuerPublicKey(nil)}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the signature is empty", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + nil, + []byte("a digest"), + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: handlers.NewRevocationPublicKey(nil), + }, + ) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is empty", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + nil, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option is not of type *IdemixSignerOpts", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixCredentialRequestSignerOpts{}, + ) + Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the option's revocation public key is not of type *revocationPublicKey", func() { + It("returns error", func() { + valid, err := Verifier.Verify( + handlers.NewIssuerPublicKey(nil), + []byte("fake signature"), + nil, + &bccsp.IdemixSignerOpts{RevocationPublicKey: handlers.NewUserSecretKey(nil, false)}, + ) + Expect(err).To(MatchError("invalid options, expected *revocationPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + }) + }) + + Describe("when verifying a nym eid", func() { + + var ( + Verifier *handlers.Verifier + fakeSignatureScheme *mock.SignatureScheme + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.SignatureScheme{} + Verifier = &handlers.Verifier{SignatureScheme: fakeSignatureScheme} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeSignatureScheme.AuditNymEidReturns(nil) + }) + + It("returns no error and a successful validation", func() { + valid, err := Verifier.AuditNymEid( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.EidNymAuditOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm falis", func() { + BeforeEach(func() { + fakeSignatureScheme.AuditNymEidReturns(errors.New("invalid nym eid")) + }) + + It("returns an error and a falied validation", func() { + valid, err := Verifier.AuditNymEid( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.EidNymAuditOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid nym eid")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the wrong option is supplied", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymEid( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixSignerOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid options, expected *EidNymAuditOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and no signature is supplied", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymEid( + handlers.NewIssuerPublicKey(nil), + nil, + []byte("a digest"), + &bccsp.EidNymAuditOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the issuer public key is nil", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymEid( + nil, + []byte("fake signature"), + nil, + &bccsp.EidNymAuditOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + }) + + Describe("when verifying a nym eid and a nym rh", func() { + + var ( + Verifier *handlers.Verifier + fakeSignatureScheme *mock.SignatureScheme + ) + + BeforeEach(func() { + fakeSignatureScheme = &mock.SignatureScheme{} + Verifier = &handlers.Verifier{SignatureScheme: fakeSignatureScheme} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + BeforeEach(func() { + fakeSignatureScheme.AuditNymRhReturns(nil) + }) + + It("returns no error and a successful validation", func() { + valid, err := Verifier.AuditNymRh( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.RhNymAuditOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + }) + + Context("and the underlying cryptographic algorithm falis", func() { + BeforeEach(func() { + fakeSignatureScheme.AuditNymRhReturns(errors.New("invalid nym rh")) + }) + + It("returns an error and a failed validation", func() { + valid, err := Verifier.AuditNymRh( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.RhNymAuditOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid nym rh")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the wrong option is supplied", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymRh( + handlers.NewIssuerPublicKey(nil), + []byte("a signature"), + []byte("a digest"), + &bccsp.IdemixSignerOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid options, expected *RhNymAuditOpts")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and no signature is supplied", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymRh( + handlers.NewIssuerPublicKey(nil), + nil, + []byte("a digest"), + &bccsp.RhNymAuditOpts{}, + ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + }) + + Context("and the issuer public key is nil", func() { + It("returns error", func() { + valid, err := Verifier.AuditNymRh( + nil, + []byte("fake signature"), + nil, + &bccsp.RhNymAuditOpts{}, + ) + Expect(err).To(MatchError("invalid key, expected *issuerPublicKey")) + Expect(valid).To(BeFalse()) + }) + }) + }) + +}) diff --git a/v2/bccsp/handlers/user.go b/v2/bccsp/handlers/user.go new file mode 100644 index 0000000..34295f7 --- /dev/null +++ b/v2/bccsp/handlers/user.go @@ -0,0 +1,98 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers + +import ( + "crypto/sha256" + + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// UserSecretKey contains the User secret key +type UserSecretKey struct { + // Sk is the idemix reference to the User key + Sk *math.Zr + // Exportable if true, sk can be exported via the Bytes function + Exportable bool +} + +func NewUserSecretKey(sk *math.Zr, exportable bool) *UserSecretKey { + return &UserSecretKey{Sk: sk, Exportable: exportable} +} + +func (k *UserSecretKey) Bytes() ([]byte, error) { + if k.Exportable { + return k.Sk.Bytes(), nil + } + + return nil, errors.New("not exportable") +} + +func (k *UserSecretKey) SKI() []byte { + raw := k.Sk.Bytes() + hash := sha256.New() + hash.Write(raw) + return hash.Sum(nil) +} + +func (*UserSecretKey) Symmetric() bool { + return true +} + +func (*UserSecretKey) Private() bool { + return true +} + +func (k *UserSecretKey) PublicKey() (bccsp.Key, error) { + return nil, errors.New("cannot call this method on a symmetric key") +} + +type UserKeyGen struct { + // Exportable is a flag to allow an issuer secret key to be marked as Exportable. + // If a secret key is marked as Exportable, its Bytes method will return the key's byte representation. + Exportable bool + // User implements the underlying cryptographic algorithms + User types.User +} + +func (g *UserKeyGen) KeyGen(opts bccsp.KeyGenOpts) (bccsp.Key, error) { + sk, err := g.User.NewKey() + if err != nil { + return nil, err + } + + return &UserSecretKey{Exportable: g.Exportable, Sk: sk}, nil +} + +// UserKeyImporter import user keys +type UserKeyImporter struct { + // Exportable is a flag to allow a secret key to be marked as Exportable. + // If a secret key is marked as Exportable, its Bytes method will return the key's byte representation. + Exportable bool + // User implements the underlying cryptographic algorithms + User types.User +} + +func (i *UserKeyImporter) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + der, ok := raw.([]byte) + if !ok { + return nil, errors.New("invalid raw, expected byte array") + } + + if len(der) == 0 { + return nil, errors.New("invalid raw, it must not be nil") + } + + sk, err := i.User.NewKeyFromBytes(raw.([]byte)) + if err != nil { + return nil, err + } + + return &UserSecretKey{Exportable: i.Exportable, Sk: sk}, nil +} diff --git a/v2/bccsp/handlers/user_test.go b/v2/bccsp/handlers/user_test.go new file mode 100644 index 0000000..0190135 --- /dev/null +++ b/v2/bccsp/handlers/user_test.go @@ -0,0 +1,421 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package handlers_test + +import ( + "crypto/sha256" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/errors" +) + +var _ = Describe("User", func() { + + var ( + fakeUser *mock.User + fakeUserSecretKey bccsp.Key + ) + + BeforeEach(func() { + fakeUser = &mock.User{} + }) + + Describe("when creating a user key", func() { + var ( + UserKeyGen *handlers.UserKeyGen + ) + + BeforeEach(func() { + UserKeyGen = &handlers.UserKeyGen{} + UserKeyGen.User = fakeUser + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + sk bccsp.Key + fakeIdemixKey *math.Zr + SKI []byte + ) + BeforeEach(func() { + fakeIdemixKey = math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + + fakeUser.NewKeyReturns(fakeIdemixKey, nil) + hash := sha256.New() + hash.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + SKI = hash.Sum(nil) + + fakeUserSecretKey = handlers.NewUserSecretKey(fakeIdemixKey, false) + }) + + AfterEach(func() { + Expect(sk.Private()).To(BeTrue()) + Expect(sk.Symmetric()).To(BeTrue()) + Expect(sk.SKI()).NotTo(BeNil()) + Expect(sk.SKI()).To(BeEquivalentTo(SKI)) + + pk, err := sk.PublicKey() + Expect(err).To(MatchError("cannot call this method on a symmetric key")) + Expect(pk).To(BeNil()) + }) + + Context("and the secret key is exportable", func() { + BeforeEach(func() { + UserKeyGen.Exportable = true + fakeUserSecretKey = handlers.NewUserSecretKey(fakeIdemixKey, true) + }) + + It("returns no error and a key", func() { + var err error + sk, err = UserKeyGen.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(fakeUserSecretKey)) + + raw, err := sk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + Expect(raw).To(BeEquivalentTo([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) + }) + }) + + Context("and the secret key is not exportable", func() { + BeforeEach(func() { + UserKeyGen.Exportable = false + fakeUserSecretKey = handlers.NewUserSecretKey(fakeIdemixKey, false) + }) + + It("returns no error and a key", func() { + sk, err := UserKeyGen.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + Expect(sk).To(BeEquivalentTo(fakeUserSecretKey)) + + raw, err := sk.Bytes() + Expect(err).To(MatchError("not exportable")) + Expect(raw).To(BeNil()) + }) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeUser.NewKeyReturns(nil, errors.New("new-key error")) + }) + + It("returns an error", func() { + keyPair, err := UserKeyGen.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{}) + Expect(err).To(MatchError("new-key error")) + Expect(keyPair).To(BeNil()) + }) + }) + + }) + + Describe("when deriving a new pseudonym", func() { + var ( + NymKeyDerivation *handlers.NymKeyDerivation + fakeIssuerPublicKey bccsp.Key + ) + + BeforeEach(func() { + NymKeyDerivation = &handlers.NymKeyDerivation{Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + NymKeyDerivation.User = fakeUser + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + var ( + nym bccsp.Key + userKey *math.Zr + fakeNym bccsp.Key + result2 *math.Zr + result1 *math.G1 + ) + + BeforeEach(func() { + userKey = math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + result2 = math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + result1 = math.Curves[math.FP256BN_AMCL].GenG1 + + fakeUser.MakeNymReturns(result1, result2, nil) + }) + + AfterEach(func() { + Expect(nym.Private()).To(BeTrue()) + Expect(nym.Symmetric()).To(BeFalse()) + Expect(nym.SKI()).NotTo(BeNil()) + + pk, err := nym.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + Expect(pk.Private()).To(BeFalse()) + Expect(pk.Symmetric()).To(BeFalse()) + Expect(pk.SKI()).NotTo(BeNil()) + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + + pk2, err := pk.PublicKey() + Expect(err).NotTo(HaveOccurred()) + Expect(pk).To(BeEquivalentTo(pk2)) + }) + + Context("and the secret key is exportable", func() { + BeforeEach(func() { + var err error + NymKeyDerivation.Exportable = true + fakeUserSecretKey = handlers.NewUserSecretKey(userKey, true) + fakeIssuerPublicKey = handlers.NewIssuerPublicKey(nil) + fakeNym, err = handlers.NewNymSecretKey(result2, result1, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}, true) + Expect(err).NotTo(HaveOccurred()) + }) + + It("returns no error and a key", func() { + var err error + nym, err = NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &bccsp.IdemixNymKeyDerivationOpts{IssuerPK: fakeIssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + Expect(nym).To(BeEquivalentTo(fakeNym)) + + raw, err := nym.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + }) + }) + + Context("and the secret key is not exportable", func() { + BeforeEach(func() { + var err error + NymKeyDerivation.Exportable = false + fakeUserSecretKey = handlers.NewUserSecretKey(userKey, false) + fakeNym, err = handlers.NewNymSecretKey(result2, result1, &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}, false) + Expect(err).NotTo(HaveOccurred()) + }) + + It("returns no error and a key", func() { + var err error + nym, err = NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &bccsp.IdemixNymKeyDerivationOpts{IssuerPK: fakeIssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + Expect(nym).To(BeEquivalentTo(fakeNym)) + + raw, err := nym.Bytes() + Expect(err).To(HaveOccurred()) + Expect(raw).To(BeNil()) + }) + + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + BeforeEach(func() { + fakeUserSecretKey = handlers.NewUserSecretKey(nil, true) + fakeIssuerPublicKey = handlers.NewIssuerPublicKey(nil) + fakeUser.MakeNymReturns(nil, nil, errors.New("make-nym error")) + }) + + It("returns an error", func() { + nym, err := NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &bccsp.IdemixNymKeyDerivationOpts{IssuerPK: fakeIssuerPublicKey}) + Expect(err).To(MatchError("make-nym error")) + Expect(nym).To(BeNil()) + }) + }) + + Context("and the options are not well formed", func() { + + Context("and the user secret key is nil", func() { + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(nil, &bccsp.IdemixNymKeyDerivationOpts{}) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(nym).To(BeNil()) + }) + }) + + Context("and the user secret key is not of type *userSecretKey", func() { + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(handlers.NewIssuerPublicKey(nil), &bccsp.IdemixNymKeyDerivationOpts{}) + Expect(err).To(MatchError("invalid key, expected *userSecretKey")) + Expect(nym).To(BeNil()) + }) + }) + + Context("and the option is missing", func() { + BeforeEach(func() { + fakeUserSecretKey = handlers.NewUserSecretKey(nil, false) + }) + + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(fakeUserSecretKey, nil) + Expect(err).To(MatchError("invalid options, expected *IdemixNymKeyDerivationOpts")) + Expect(nym).To(BeNil()) + }) + }) + + Context("and the option is not of type *bccsp.IdemixNymKeyDerivationOpts", func() { + BeforeEach(func() { + fakeUserSecretKey = handlers.NewUserSecretKey(nil, false) + }) + + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &WrongOpts{}) + Expect(err).To(MatchError("invalid options, expected *IdemixNymKeyDerivationOpts")) + Expect(nym).To(BeNil()) + }) + }) + + Context("and the issuer public key is missing", func() { + BeforeEach(func() { + fakeUserSecretKey = handlers.NewUserSecretKey(nil, false) + }) + + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &bccsp.IdemixNymKeyDerivationOpts{}) + Expect(err).To(MatchError("invalid options, missing issuer public key")) + Expect(nym).To(BeNil()) + }) + + }) + + Context("and the issuer public key is not of type *issuerPublicKey", func() { + BeforeEach(func() { + fakeUserSecretKey = handlers.NewUserSecretKey(nil, false) + }) + + It("returns error", func() { + nym, err := NymKeyDerivation.KeyDeriv(fakeUserSecretKey, &bccsp.IdemixNymKeyDerivationOpts{IssuerPK: fakeUserSecretKey}) + Expect(err).To(MatchError("invalid options, expected IssuerPK as *issuerPublicKey")) + Expect(nym).To(BeNil()) + }) + + }) + }) + }) + + Context("when importing a user key", func() { + var ( + UserKeyImporter *handlers.UserKeyImporter + ) + + BeforeEach(func() { + UserKeyImporter = &handlers.UserKeyImporter{Exportable: true, User: fakeUser} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + + BeforeEach(func() { + sk := math.Curves[math.FP256BN_AMCL].NewZrFromInt(1) + + fakeUser.NewKeyFromBytesReturns(sk, nil) + }) + + It("import is successful", func() { + k, err := UserKeyImporter.KeyImport([]byte("fake-raw"), nil) + Expect(err).NotTo(HaveOccurred()) + + bytes, err := k.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(bytes).To(BeEquivalentTo([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + + BeforeEach(func() { + fakeUser.NewKeyFromBytesReturns(nil, errors.New("new-public-key-nym-import-err")) + }) + + It("returns an error on nil raw", func() { + k, err := UserKeyImporter.KeyImport(nil, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error on empty raw", func() { + k, err := UserKeyImporter.KeyImport([]byte{}, nil) + Expect(err).To(MatchError("invalid raw, it must not be nil")) + Expect(k).To(BeNil()) + }) + + It("returns an error on invalid raw", func() { + k, err := UserKeyImporter.KeyImport(UserKeyImporter, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error", func() { + k, err := UserKeyImporter.KeyImport([]byte("fake-raw"), nil) + Expect(err).To(MatchError("new-public-key-nym-import-err")) + Expect(k).To(BeNil()) + }) + + }) + + }) + + Context("when importing a nym public key", func() { + var ( + NymPublicKeyImporter *handlers.NymPublicKeyImporter + ) + + BeforeEach(func() { + NymPublicKeyImporter = &handlers.NymPublicKeyImporter{User: fakeUser, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("and the underlying cryptographic algorithm succeed", func() { + + BeforeEach(func() { + ecp := math.Curves[math.FP256BN_AMCL].GenG1 + + fakeUser.NewPublicNymFromBytesReturns(ecp, nil) + }) + + It("import is successful", func() { + k, err := NymPublicKeyImporter.KeyImport([]byte("fake-raw"), nil) + Expect(err).NotTo(HaveOccurred()) + + bytes, err := k.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(bytes).To(BeEquivalentTo([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})) + }) + }) + + Context("and the underlying cryptographic algorithm fails", func() { + + BeforeEach(func() { + fakeUser.NewPublicNymFromBytesReturns(nil, errors.New("new-public-key-nym-import-err")) + }) + + It("returns an error on nil raw", func() { + k, err := NymPublicKeyImporter.KeyImport(nil, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error on empty raw", func() { + k, err := NymPublicKeyImporter.KeyImport([]byte{}, nil) + Expect(err).To(MatchError("invalid raw, it must not be nil")) + Expect(k).To(BeNil()) + }) + + It("returns an error on invalid raw", func() { + k, err := NymPublicKeyImporter.KeyImport(NymPublicKeyImporter, nil) + Expect(err).To(MatchError("invalid raw, expected byte array")) + Expect(k).To(BeNil()) + }) + + It("returns an error", func() { + k, err := NymPublicKeyImporter.KeyImport([]byte("fake-raw"), nil) + Expect(err).To(MatchError("new-public-key-nym-import-err")) + Expect(k).To(BeNil()) + }) + + }) + + }) + +}) diff --git a/v2/bccsp/idemix_suite_test.go b/v2/bccsp/idemix_suite_test.go new file mode 100644 index 0000000..9dc53f1 --- /dev/null +++ b/v2/bccsp/idemix_suite_test.go @@ -0,0 +1,18 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPlain(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plain Suite") +} diff --git a/v2/bccsp/impl.go b/v2/bccsp/impl.go new file mode 100644 index 0000000..80507ea --- /dev/null +++ b/v2/bccsp/impl.go @@ -0,0 +1,410 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package idemix + +import ( + "hash" + "reflect" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/v2/common/flogging" + "github.com/pkg/errors" +) + +var ( + logger = flogging.MustGetLogger("bccsp_idemix") +) + +// KeyGenerator is a BCCSP-like interface that provides key generation algorithms +type KeyGenerator interface { + + // KeyGen generates a key using opts. + KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) +} + +// KeyDeriver is a BCCSP-like interface that provides key derivation algorithms +type KeyDeriver interface { + + // KeyDeriv derives a key from k using opts. + // The opts argument should be appropriate for the primitive used. + KeyDeriv(k bccsp.Key, opts bccsp.KeyDerivOpts) (dk bccsp.Key, err error) +} + +// KeyImporter is a BCCSP-like interface that provides key import algorithms +type KeyImporter interface { + + // KeyImport imports a key from its raw representation using opts. + // The opts argument should be appropriate for the primitive used. + KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) +} + +// Encryptor is a BCCSP-like interface that provides encryption algorithms +type Encryptor interface { + + // Encrypt encrypts plaintext using key k. + // The opts argument should be appropriate for the algorithm used. + Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) +} + +// Decryptor is a BCCSP-like interface that provides decryption algorithms +type Decryptor interface { + + // Decrypt decrypts ciphertext using key k. + // The opts argument should be appropriate for the algorithm used. + Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) +} + +// Signer is a BCCSP-like interface that provides signing algorithms +type Signer interface { + + // Sign signs digest using key k. + // The opts argument should be appropriate for the algorithm used. + // + // Note that when a signature of a hash of a larger message is needed, + // the caller is responsible for hashing the larger message and passing + // the hash (as digest). + Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) +} + +// Verifier is a BCCSP-like interface that provides verifying algorithms +type Verifier interface { + + // Verify verifies signature against key k and digest + // The opts argument should be appropriate for the algorithm used. + Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) +} + +// Hasher is a BCCSP-like interface that provides hash algorithms +type Hasher interface { + + // Hash hashes messages msg using options opts. + // If opts is nil, the default hash function will be used. + Hash(msg []byte, opts bccsp.HashOpts) (hash []byte, err error) + + // GetHash returns and instance of hash.Hash using options opts. + // If opts is nil, the default hash function will be returned. + GetHash(opts bccsp.HashOpts) (h hash.Hash, err error) +} + +// CSP provides a generic implementation of the BCCSP interface based +// on wrappers. It can be customized by providing implementations for the +// following algorithm-based wrappers: KeyGenerator, KeyDeriver, KeyImporter, +// Encryptor, Decryptor, Signer, Verifier, Hasher. Each wrapper is bound to a +// goland type representing either an option or a key. +type CSP struct { + ks bccsp.KeyStore + + KeyGenerators map[reflect.Type]KeyGenerator + KeyDerivers map[reflect.Type]KeyDeriver + KeyImporters map[reflect.Type]KeyImporter + Encryptors map[reflect.Type]Encryptor + Decryptors map[reflect.Type]Decryptor + Signers map[reflect.Type]Signer + Verifiers map[reflect.Type]Verifier + Hashers map[reflect.Type]Hasher +} + +func NewImpl(keyStore bccsp.KeyStore) (*CSP, error) { + if keyStore == nil { + return nil, errors.Errorf("Invalid bccsp.KeyStore instance. It must be different from nil.") + } + + encryptors := make(map[reflect.Type]Encryptor) + decryptors := make(map[reflect.Type]Decryptor) + signers := make(map[reflect.Type]Signer) + verifiers := make(map[reflect.Type]Verifier) + hashers := make(map[reflect.Type]Hasher) + keyGenerators := make(map[reflect.Type]KeyGenerator) + keyDerivers := make(map[reflect.Type]KeyDeriver) + keyImporters := make(map[reflect.Type]KeyImporter) + + csp := &CSP{keyStore, + keyGenerators, keyDerivers, keyImporters, encryptors, + decryptors, signers, verifiers, hashers} + + return csp, nil +} + +// KeyGen generates a key using opts. +func (csp *CSP) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) { + // Validate arguments + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + keyGenerator, found := csp.KeyGenerators[reflect.TypeOf(opts)] + if !found { + return nil, errors.Errorf("Unsupported 'KeyGenOpts' provided [%v]", opts) + } + + k, err = keyGenerator.KeyGen(opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed generating key with opts [%v]", opts) + } + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.StoreKey(k) + if err != nil { + return nil, errors.Wrapf(err, "Failed storing key [%s]", opts.Algorithm()) + } + } + + return k, nil +} + +// KeyDeriv derives a key from k using opts. +// The opts argument should be appropriate for the primitive used. +func (csp *CSP) KeyDeriv(k bccsp.Key, opts bccsp.KeyDerivOpts) (dk bccsp.Key, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + if opts == nil { + return nil, errors.New("Invalid opts. It must not be nil.") + } + + keyDeriver, found := csp.KeyDerivers[reflect.TypeOf(k)] + if !found { + return nil, errors.Errorf("Unsupported 'Key' provided [%v]", k) + } + + k, err = keyDeriver.KeyDeriv(k, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed deriving key with opts [%v]", opts) + } + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.StoreKey(k) + if err != nil { + return nil, errors.Wrapf(err, "Failed storing key [%s]", opts.Algorithm()) + } + } + + return k, nil +} + +// KeyImport imports a key from its raw representation using opts. +// The opts argument should be appropriate for the primitive used. +func (csp *CSP) KeyImport(raw interface{}, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + // Validate arguments + if raw == nil { + return nil, errors.New("Invalid raw. It must not be nil.") + } + if opts == nil { + return nil, errors.New("Invalid opts. It must not be nil.") + } + + keyImporter, found := csp.KeyImporters[reflect.TypeOf(opts)] + if !found { + return nil, errors.Errorf("Unsupported 'KeyImportOpts' provided [%v]", opts) + } + + k, err = keyImporter.KeyImport(raw, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed importing key with opts [%v]", opts) + } + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.StoreKey(k) + if err != nil { + return nil, errors.Wrapf(err, "Failed storing imported key with opts [%v]", opts) + } + } + + return +} + +// GetKey returns the key this CSP associates to +// the Subject Key Identifier ski. +func (csp *CSP) GetKey(ski []byte) (k bccsp.Key, err error) { + k, err = csp.ks.GetKey(ski) + if err != nil { + return nil, errors.Wrapf(err, "Failed getting key for SKI [%v]", ski) + } + + return +} + +// Hash hashes messages msg using options opts. +func (csp *CSP) Hash(msg []byte, opts bccsp.HashOpts) (digest []byte, err error) { + // Validate arguments + if opts == nil { + return nil, errors.New("Invalid opts. It must not be nil.") + } + + hasher, found := csp.Hashers[reflect.TypeOf(opts)] + if !found { + return nil, errors.Errorf("Unsupported 'HashOpt' provided [%v]", opts) + } + + digest, err = hasher.Hash(msg, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed hashing with opts [%v]", opts) + } + + return +} + +// GetHash returns and instance of hash.Hash using options opts. +// If opts is nil then the default hash function is returned. +func (csp *CSP) GetHash(opts bccsp.HashOpts) (h hash.Hash, err error) { + // Validate arguments + if opts == nil { + return nil, errors.New("Invalid opts. It must not be nil.") + } + + hasher, found := csp.Hashers[reflect.TypeOf(opts)] + if !found { + return nil, errors.Errorf("Unsupported 'HashOpt' provided [%v]", opts) + } + + h, err = hasher.GetHash(opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed getting hash function with opts [%v]", opts) + } + + return +} + +// Sign signs digest using key k. +// The opts argument should be appropriate for the primitive used. +// +// Note that when a signature of a hash of a larger message is needed, +// the caller is responsible for hashing the larger message and passing +// the hash (as digest). +func (csp *CSP) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + if len(digest) == 0 { + return nil, errors.New("Invalid digest. Cannot be empty.") + } + + keyType := reflect.TypeOf(k) + signer, found := csp.Signers[keyType] + if !found { + return nil, errors.Errorf("Unsupported 'SignKey' provided [%s]", keyType) + } + + signature, err = signer.Sign(k, digest, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed signing with opts [%v]", opts) + } + + return +} + +// Verify verifies signature against key k and digest +func (csp *CSP) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) { + // Validate arguments + if k == nil { + return false, errors.New("Invalid Key. It must not be nil.") + } + if len(signature) == 0 { + return false, errors.New("Invalid signature. Cannot be empty.") + } + if len(digest) == 0 { + return false, errors.New("Invalid digest. Cannot be empty.") + } + + verifier, found := csp.Verifiers[reflect.TypeOf(k)] + if !found { + return false, errors.Errorf("Unsupported 'VerifyKey' provided [%v]", k) + } + + valid, err = verifier.Verify(k, signature, digest, opts) + if err != nil { + return false, errors.Wrapf(err, "Failed verifing with opts [%v]", opts) + } + + return +} + +// Encrypt encrypts plaintext using key k. +// The opts argument should be appropriate for the primitive used. +func (csp *CSP) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) ([]byte, error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + + encryptor, found := csp.Encryptors[reflect.TypeOf(k)] + if !found { + return nil, errors.Errorf("Unsupported 'EncryptKey' provided [%v]", k) + } + + return encryptor.Encrypt(k, plaintext, opts) +} + +// Decrypt decrypts ciphertext using key k. +// The opts argument should be appropriate for the primitive used. +func (csp *CSP) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + + decryptor, found := csp.Decryptors[reflect.TypeOf(k)] + if !found { + return nil, errors.Errorf("Unsupported 'DecryptKey' provided [%v]", k) + } + + plaintext, err = decryptor.Decrypt(k, ciphertext, opts) + if err != nil { + return nil, errors.Wrapf(err, "Failed decrypting with opts [%v]", opts) + } + + return +} + +// AddWrapper binds the passed type to the passed wrapper. +// Notice that that wrapper must be an instance of one of the following interfaces: +// KeyGenerator, KeyDeriver, KeyImporter, Encryptor, Decryptor, Signer, Verifier, Hasher. +func (csp *CSP) AddWrapper(t reflect.Type, w interface{}) error { + if t == nil { + return errors.Errorf("type cannot be nil") + } + if w == nil { + return errors.Errorf("wrapper cannot be nil") + } + switch dt := w.(type) { + case KeyGenerator: + csp.KeyGenerators[t] = dt + case KeyImporter: + csp.KeyImporters[t] = dt + case KeyDeriver: + csp.KeyDerivers[t] = dt + case Encryptor: + csp.Encryptors[t] = dt + case Decryptor: + csp.Decryptors[t] = dt + case Signer: + csp.Signers[t] = dt + case Verifier: + csp.Verifiers[t] = dt + case Hasher: + csp.Hashers[t] = dt + default: + return errors.Errorf("wrapper type not valid, must be on of: KeyGenerator, KeyDeriver, KeyImporter, Encryptor, Decryptor, Signer, Verifier, Hasher") + } + return nil +} diff --git a/v2/bccsp/keystore/dummy.go b/v2/bccsp/keystore/dummy.go new file mode 100644 index 0000000..c000038 --- /dev/null +++ b/v2/bccsp/keystore/dummy.go @@ -0,0 +1,34 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package keystore + +import ( + "errors" + + bccsp "github.com/IBM/idemix/bccsp/types" +) + +// Dummy is a read-only KeyStore that neither loads nor stores keys. +type Dummy struct { +} + +// ReadOnly returns true if this KeyStore is read only, false otherwise. +// If ReadOnly is true then StoreKey will fail. +func (ks *Dummy) ReadOnly() bool { + return true +} + +// GetKey returns a key object whose SKI is the one passed. +func (ks *Dummy) GetKey(ski []byte) (bccsp.Key, error) { + return nil, errors.New("key not found. This is a dummy KeyStore") +} + +// StoreKey stores the key k in this KeyStore. +// If this KeyStore is read only then the method will fail. +func (ks *Dummy) StoreKey(k bccsp.Key) error { + return errors.New("cannot store key. This is a dummy read-only KeyStore") +} diff --git a/v2/bccsp/keystore/kvs/filebased.go b/v2/bccsp/keystore/kvs/filebased.go new file mode 100644 index 0000000..b5c1eaa --- /dev/null +++ b/v2/bccsp/keystore/kvs/filebased.go @@ -0,0 +1,69 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package kvs + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + + "github.com/pkg/errors" +) + +type FileBasedKVS struct { + path string +} + +func NewFileBased(path string) (*FileBasedKVS, error) { + f, err := os.Stat(path) + + if !os.IsNotExist(err) && f.Mode().IsRegular() { + return nil, errors.Errorf("invalid path [%s]: it's a file", path) + } + + if os.IsNotExist(err) { + err = os.MkdirAll(path, 0770) + if err != nil { + return nil, errors.Wrapf(err, "could not create path [%s]", path) + } + } + + return &FileBasedKVS{ + path: path, + }, nil +} + +func (f *FileBasedKVS) Put(id string, entry interface{}) error { + bytes, err := json.Marshal(entry) + if err != nil { + return errors.Wrapf(err, "marshalling key [%s] failed", id) + } + + fname := path.Join(f.path, id) + err = ioutil.WriteFile(fname, bytes, 0660) + if err != nil { + return errors.Wrapf(err, "writing [%s] failed", fname) + } + + return nil +} + +func (f *FileBasedKVS) Get(id string, entry interface{}) error { + fname := path.Join(f.path, id) + bytes, err := ioutil.ReadFile(fname) + if err != nil { + return errors.Wrapf(err, "could not read file [%s]", fname) + } + + err = json.Unmarshal(bytes, entry) + if err != nil { + return errors.Wrapf(err, "could not unmarshal bytes for file [%s]", fname) + } + + return nil +} diff --git a/v2/bccsp/keystore/kvsbased.go b/v2/bccsp/keystore/kvsbased.go new file mode 100644 index 0000000..7f5067e --- /dev/null +++ b/v2/bccsp/keystore/kvsbased.go @@ -0,0 +1,121 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package keystore + +import ( + "encoding/hex" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/v2/bccsp/handlers" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +type KVS interface { + Put(id string, state interface{}) error + Get(id string, state interface{}) error +} + +type NymSecretKey struct { + Ski []byte + Sk []byte + Pk *amcl.ECP + Exportable bool +} + +type UserSecretKey struct { + Sk []byte + Exportable bool +} + +type entry struct { + NymSecretKey *NymSecretKey `json:",omitempty"` + UserSecretKey *UserSecretKey `json:",omitempty"` +} + +// KVSStore is a read-only KeyStore that neither loads nor stores keys. +type KVSStore struct { + KVS + Translator idemix.Translator + Curve *math.Curve +} + +// ReadOnly returns true if this KeyStore is read only, false otherwise. +// If ReadOnly is true then StoreKey will fail. +func (ks *KVSStore) ReadOnly() bool { + return false +} + +// GetKey returns a key object whose SKI is the one passed. +func (ks *KVSStore) GetKey(ski []byte) (bccsp.Key, error) { + id := hex.EncodeToString(ski) + + entry := &entry{} + err := ks.KVS.Get(id, entry) + if err != nil { + return nil, errors.Wrapf(err, "could not get key [%s] from kvs", id) + } + + switch { + case entry.NymSecretKey != nil: + pk, err := ks.Translator.G1FromProto(entry.NymSecretKey.Pk) + if err != nil { + return nil, err + } + + return &handlers.NymSecretKey{ + Exportable: entry.NymSecretKey.Exportable, + Sk: ks.Curve.NewZrFromBytes(entry.NymSecretKey.Sk), + Ski: entry.NymSecretKey.Ski, + Pk: pk, + Translator: ks.Translator, + }, nil + case entry.UserSecretKey != nil: + return &handlers.UserSecretKey{ + Exportable: entry.UserSecretKey.Exportable, + Sk: ks.Curve.NewZrFromBytes(entry.UserSecretKey.Sk), + }, nil + default: + return nil, errors.Errorf("key not found for [%s]", id) + } +} + +// StoreKey stores the key k in this KeyStore. +// If this KeyStore is read only then the method will fail. +func (ks *KVSStore) StoreKey(k bccsp.Key) error { + entry := &entry{} + var id string + + switch key := k.(type) { + case *handlers.NymSecretKey: + entry.NymSecretKey = &NymSecretKey{ + Ski: key.Ski, + Sk: key.Sk.Bytes(), + Pk: ks.Translator.G1ToProto(key.Pk), + Exportable: key.Exportable, + } + + pk, err := k.PublicKey() + if err != nil { + return errors.Errorf("could not get public version for key [%s]", k.SKI()) + } + + id = hex.EncodeToString(pk.SKI()) + case *handlers.UserSecretKey: + entry.UserSecretKey = &UserSecretKey{ + Sk: key.Sk.Bytes(), + Exportable: key.Exportable, + } + id = hex.EncodeToString(k.SKI()) + default: + return errors.Errorf("unknown type [%T] for the supplied key", key) + } + + return ks.KVS.Put(id, entry) +} diff --git a/v2/bccsp/keystore/kvsbased_test.go b/v2/bccsp/keystore/kvsbased_test.go new file mode 100644 index 0000000..3606f74 --- /dev/null +++ b/v2/bccsp/keystore/kvsbased_test.go @@ -0,0 +1,81 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package keystore + +import ( + "io/ioutil" + "os" + "testing" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/keystore/kvs" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/stretchr/testify/assert" +) + +func TestFileBased(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + translator := &amcl.Fp256bn{C: curve} + rnd, err := curve.Rand() + assert.NoError(t, err) + + dir, err := ioutil.TempDir(os.TempDir(), "idemix-TesFileBased") + assert.NoError(t, err) + defer os.RemoveAll(dir) + + kvs, err := kvs.NewFileBased(dir) + assert.NoError(t, err) + + keystore := &KVSStore{ + KVS: kvs, + Translator: translator, + Curve: curve, + } + + nsk, err := handlers.NewNymSecretKey(curve.NewRandomZr(rnd), curve.GenG1.Mul(curve.NewRandomZr(rnd)), translator, true) + assert.NoError(t, err) + keys := []bccsp.Key{ + handlers.NewUserSecretKey(curve.NewRandomZr(rnd), true), + nsk, + } + pk, err := nsk.PublicKey() + assert.NoError(t, err) + skis := [][]byte{ + keys[0].SKI(), + pk.SKI(), + } + + for i, key := range keys { + err = keystore.StoreKey(key) + assert.NoError(t, err) + + keyBack, err := keystore.GetKey(skis[i]) + assert.NoError(t, err) + + b1, err := key.Bytes() + assert.NoError(t, err) + b2, err := keyBack.Bytes() + assert.NoError(t, err) + assert.Equal(t, b1, b2) + + pk1, err := key.PublicKey() + if err != nil && err.Error() == "cannot call this method on a symmetric key" { + // skip if it's a symmetric key + continue + } + pk2, err := keyBack.PublicKey() + assert.NoError(t, err) + + b1, err = pk1.Bytes() + assert.NoError(t, err) + b2, err = pk2.Bytes() + assert.NoError(t, err) + assert.Equal(t, b1, b2) + } +} diff --git a/v2/bccsp/legacy_test.go b/v2/bccsp/legacy_test.go new file mode 100644 index 0000000..6d52658 --- /dev/null +++ b/v2/bccsp/legacy_test.go @@ -0,0 +1,1809 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix_test + +import ( + "crypto/rand" + "fmt" + "io/ioutil" + "os" + "path" + + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp" + idemix1 "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func testWithCurve(id math.CurveID, translator idemix1.Translator) { + Describe(fmt.Sprintf("setting up the environment with one issuer and one user with curve %s", curveName(id)), func() { + var ( + CSP bccsp.BCCSP + IssuerKey bccsp.Key + IssuerPublicKey bccsp.Key + AttributeNames []string + + UserKey bccsp.Key + NymKey bccsp.Key + NymPublicKey bccsp.Key + + IssuerNonce []byte + credRequest []byte + + credential []byte + + RevocationKey bccsp.Key + RevocationPublicKey bccsp.Key + cri []byte + rootDir string + ) + + BeforeEach(func() { + var err error + + rootDir, err = ioutil.TempDir(os.TempDir(), "idemixtest") + Expect(err).NotTo(HaveOccurred()) + + CSP, err = idemix.New(NewDummyKeyStore(), math.Curves[id], translator, true) + Expect(err).NotTo(HaveOccurred()) + + // Issuer + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + IssuerKey, err = CSP.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err := IssuerKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "issuerkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = IssuerPublicKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "issuerkey.pk"), raw, 0666)).NotTo(HaveOccurred()) + + // User + UserKey, err = CSP.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + raw, err = UserKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + // Expect(len(raw)).To(Equal(32)) + Expect(ioutil.WriteFile(path.Join(rootDir, "userkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + + // User Nym Key + NymKey, err = CSP.KeyDeriv(UserKey, &bccsp.IdemixNymKeyDerivationOpts{Temporary: true, IssuerPK: IssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = NymKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err = NymKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "nymkey.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = NymPublicKey.Bytes() + Expect(len(raw)).To(Equal(2 * math.Curves[id].CoordByteSize)) + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "nymkey.pk"), raw, 0666)).NotTo(HaveOccurred()) + + IssuerNonce = make([]byte, math.Curves[id].ScalarByteSize) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(math.Curves[id].ScalarByteSize)) + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + credRequest, err = CSP.Sign( + UserKey, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + + // Credential + credential, err = CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + // Revocation + RevocationKey, err = CSP.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err = RevocationKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "revocation.sk"), raw, 0666)).NotTo(HaveOccurred()) + raw, err = RevocationPublicKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(ioutil.WriteFile(path.Join(rootDir, "revocation.pk"), raw, 0666)).NotTo(HaveOccurred()) + + // CRI + cri, err = CSP.Sign( + RevocationKey, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CSP.Verify( + RevocationPublicKey, + cri, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Describe("producing an idemix signature with no disclosed attribute", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid even when there's garbage in the nym eid field if we do basic verification", func() { + sig := &idemix1.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.EidNym = &idemix1.EIDNym{ + ProofSEid: []byte("invalid garbage"), + } + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do ExpectStandard verification", func() { + sig := &idemix1.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.EidNym = &idemix1.EIDNym{ + ProofSEid: []byte("invalid garbage"), + } + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("EidNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do ExpectEidNym verification", func() { + sig := &idemix1.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.EidNym = &idemix1.EIDNym{ + ProofSEid: []byte("invalid garbage"), + } + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no EidNym provided but ExpectEidNym required")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when there's garbage in the nym eid field and we do BestEffort verification", func() { + sig := &idemix1.Signature{} + err := proto.Unmarshal(signature, sig) + Expect(err).NotTo(HaveOccurred()) + + sig.EidNym = &idemix1.EIDNym{ + ProofSEid: []byte("invalid garbageinvalid garbageinvalid garbageinvalid garbageinvalid garbageinvalid garbageinvalid garbageinvalid garbage"), + Nym: translator.G1ToProto(math.Curves[id].GenG1), + } + + sigBytes, err := proto.Marshal(sig) + Expect(err).NotTo(HaveOccurred()) + + valid, err := CSP.Verify( + IssuerPublicKey, + sigBytes, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("zero-knowledge proof is invalid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect a signature with nym eid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no EidNym provided but ExpectEidNym required")) + Expect(valid).To(BeFalse()) + }) + + }) + + Describe("producing an idemix signature with an eid nym", func() { + var ( + digest []byte + signature []byte + signOpts *bccsp.IdemixSignerOpts + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signOpts = &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNym, + } + + signature, err = CSP.Sign( + UserKey, + digest, + signOpts, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts.Metadata).NotTo(BeNil()) + }) + + It("the signature is not valid if we use basic verification", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("zero-knowledge proof is invalid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect an eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect an eid nym and supply the right one", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect an eid nym and supply the wrong one", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: math.Curves[id].GenG1.Bytes(), + }, + }, + ) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, signature nym eid does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect an eid nym and supply garbage", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: []byte("garbage"), + }, + }, + ) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, failed to unmarshal meta nym eid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect an eid nym and request auditing of the eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect an eid nym and request auditing of the eid nym with a wrong randomness", func() { + signOpts.Metadata.EidNymAuditData.Rand = signOpts.Metadata.EidNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("EidNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("nym eid auditing with the right enrollment ID succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("valid signature against meta", func() { + signOpts2 := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNym, + Metadata: signOpts.Metadata, + } + signature2, err := CSP.Sign( + UserKey, + digest, + signOpts2, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts2.Metadata).NotTo(BeNil()) + + Expect(signOpts2.Metadata.EidNymAuditData.Nym.Equals(signOpts.Metadata.EidNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Attr.Equals(signOpts2.Metadata.EidNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Rand.Equals(signOpts.Metadata.EidNymAuditData.Rand)).To(BeTrue()) + + valid, err := CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + Metadata: signOpts2.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with an eid nym and rh nym", func() { + var ( + digest []byte + signature []byte + signOpts *bccsp.IdemixSignerOpts + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signOpts = &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNymRhNym, + } + + signature, err = CSP.Sign( + UserKey, + digest, + signOpts, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts.Metadata).NotTo(BeNil()) + }) + + It("the signature is not valid if we use basic verification", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + // VerificationType: bccsp.Basic, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("zero-knowledge proof is invalid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.BestEffort, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect only an eid nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNym, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("zero-knowledge proof is invalid")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is valid when we expect both an eid nym and rh nym and request auditing of the eid nym and the rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is valid when we expect both an eid nym and rh nym and supply the right eid nym and rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: signOpts.Metadata.RhNym, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the right eid nym and the wrong rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: math.Curves[id].GenG1.Bytes(), + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym rh validation failed, signature nym rh does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the right eid nym and garbage rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: signOpts.Metadata.EidNym, + RhNym: []byte("garbage"), + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym rh validation failed, failed to unmarshal meta nym rh")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and supply the wrong eid nym and the right rh nym", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: &bccsp.IdemixSignerMetadata{ + EidNym: math.Curves[id].GenG1.Bytes(), + RhNym: signOpts.Metadata.RhNym, + }, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed, signature nym eid does not match metadata")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and request auditing of the eid nym with a wrong randomness", func() { + signOpts.Metadata.EidNymAuditData.Rand = signOpts.Metadata.EidNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym eid validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect both an eid nym and rh nym and request auditing of the rh nym with a wrong randomness", func() { + signOpts.Metadata.RhNymAuditData.Rand = signOpts.Metadata.RhNymAuditData.Attr + + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("signature invalid: nym rh validation failed")) + Expect(valid).To(BeFalse()) + }) + + It("the signature is not valid when we expect a standard signature", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectStandard, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("RhNym available but ExpectStandard required")) + Expect(valid).To(BeFalse()) + }) + + It("nym eid auditing with the right enrollment ID succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: string([]byte{0, 1, 2}), + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("nym rh auditing with the right revocation handle succeeds", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.RhNymAuditOpts{ + RhIndex: 4, + RevocationHandle: string([]byte{0, 1, 2, 3}), + RNymRh: signOpts.Metadata.RhNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.RhNymAuditData.Nym.Bytes(), + digest, + &bccsp.RhNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNymRhNym, + RhIndex: 4, + RevocationHandle: string([]byte{0, 1, 2, 3}), + RNymRh: signOpts.Metadata.RhNymAuditData.Rand, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("nym eid auditing with the wrong enrollment ID fails", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.EidNymAuditOpts{ + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signOpts.Metadata.EidNymAuditData.Nym.Bytes(), + digest, + &bccsp.EidNymAuditOpts{ + AuditVerificationType: bccsp.AuditExpectEidNym, + EidIndex: 3, + EnrollmentID: "Have you seen the writing on the wall?", + RNymEid: signOpts.Metadata.EidNymAuditData.Rand, + }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("eid nym does not match")) + Expect(valid).To(BeFalse()) + }) + + It("valid signature against meta", func() { + signOpts2 := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + CRI: cri, + SigType: bccsp.EidNymRhNym, + Metadata: signOpts.Metadata, + } + signature2, err := CSP.Sign( + UserKey, + digest, + signOpts2, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signOpts2.Metadata).NotTo(BeNil()) + + Expect(signOpts2.Metadata.EidNymAuditData.Nym.Equals(signOpts.Metadata.EidNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Attr.Equals(signOpts2.Metadata.EidNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.EidNymAuditData.Rand.Equals(signOpts.Metadata.EidNymAuditData.Rand)).To(BeTrue()) + + Expect(signOpts2.Metadata.RhNymAuditData.Nym.Equals(signOpts.Metadata.RhNymAuditData.Nym)).To(BeTrue()) + Expect(signOpts2.Metadata.RhNymAuditData.Attr.Equals(signOpts2.Metadata.RhNymAuditData.Attr)).To(BeTrue()) + Expect(signOpts2.Metadata.RhNymAuditData.Rand.Equals(signOpts.Metadata.RhNymAuditData.Rand)).To(BeTrue()) + + valid, err := CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = CSP.Verify( + IssuerPublicKey, + signature2, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 3, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts2.Metadata, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with disclosed attributes", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix nym signature", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixNymSignerOpts{ + Nym: NymKey, + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + NymPublicKey, + signature, + digest, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("Idemix Bridge Load", func() { + + Describe("setting up the environment with one issuer and one user", func() { + var ( + CSP bccsp.BCCSP + IssuerKey bccsp.Key + IssuerPublicKey bccsp.Key + AttributeNames []string + + UserKey bccsp.Key + NymKey bccsp.Key + NymPublicKey bccsp.Key + + IssuerNonce []byte + credRequest []byte + + credential []byte + + RevocationKey bccsp.Key + RevocationPublicKey bccsp.Key + cri []byte + ) + + BeforeEach(func() { + var err error + CSP, err = idemix.New(NewDummyKeyStore(), math.Curves[id], translator, true) + Expect(err).NotTo(HaveOccurred()) + + // Issuer + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + raw, err := ioutil.ReadFile(path.Join(rootDir, "issuerkey.sk")) + Expect(err).NotTo(HaveOccurred()) + IssuerKey, err = CSP.KeyImport(raw, &bccsp.IdemixIssuerKeyImportOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // User + raw, err = ioutil.ReadFile(path.Join(rootDir, "userkey.sk")) + Expect(err).NotTo(HaveOccurred()) + UserKey, err = CSP.KeyImport(raw, &bccsp.IdemixUserSecretKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + // User Nym Key + rawNymKeySk, err := ioutil.ReadFile(path.Join(rootDir, "nymkey.sk")) + Expect(err).NotTo(HaveOccurred()) + rawNymKeyPk, err := ioutil.ReadFile(path.Join(rootDir, "nymkey.pk")) + Expect(err).NotTo(HaveOccurred()) + Expect(len(rawNymKeyPk)).To(Equal(2 * math.Curves[id].CoordByteSize)) + + NymKey, err = CSP.KeyImport(append(rawNymKeySk, rawNymKeyPk...), &bccsp.IdemixNymKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = CSP.KeyImport(rawNymKeyPk, &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + + IssuerNonce = make([]byte, math.Curves[id].ScalarByteSize) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(math.Curves[id].ScalarByteSize)) + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + credRequest, err = CSP.Sign( + UserKey, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + + // Credential + credential, err = CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + // Revocation + raw, err = ioutil.ReadFile(path.Join(rootDir, "revocation.sk")) + Expect(err).NotTo(HaveOccurred()) + RevocationKey, err = CSP.KeyImport(raw, &bccsp.IdemixRevocationKeyImportOpts{Temporary: true}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // CRI + cri, err = CSP.Sign( + RevocationKey, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CSP.Verify( + RevocationPublicKey, + cri, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Describe("producing an idemix signature with no disclosed attribute", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix signature with disclosed attributes", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + digest, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + + Describe("producing an idemix nym signature", func() { + var ( + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + + digest = []byte("a digest") + + signature, err = CSP.Sign( + UserKey, + digest, + &bccsp.IdemixNymSignerOpts{ + Nym: NymKey, + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := CSP.Verify( + NymPublicKey, + signature, + digest, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + }) + }) + }) + }) +} diff --git a/v2/bccsp/perf_test.go b/v2/bccsp/perf_test.go new file mode 100644 index 0000000..f01479d --- /dev/null +++ b/v2/bccsp/perf_test.go @@ -0,0 +1,302 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix_test + +import ( + "crypto/rand" + "fmt" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/IBM/idemix/bccsp/schemes/aries" + bccsp "github.com/IBM/idemix/bccsp/types" + imsp "github.com/IBM/idemix/v2" + idemix "github.com/IBM/idemix/v2/bccsp" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/stretchr/testify/assert" +) + +func setupAries(b *testing.B) (bccsp.BCCSP, bccsp.Key, bccsp.Key, bccsp.Key, []byte) { + curve := math.Curves[math.BLS12_381_BBS] + translator := &amcl.Gurvy{C: curve} + + CSP, err := idemix.NewAries(NewDummyKeyStore(), curve, translator, true) + assert.NoError(b, err) + + AttributeNames := []string{imsp.AttributeNameOU, imsp.AttributeNameRole, imsp.AttributeNameEnrollmentId, imsp.AttributeNameRevocationHandle} + IssuerKey, err := CSP.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + assert.NoError(b, err) + IssuerPublicKey, err := IssuerKey.PublicKey() + assert.NoError(b, err) + + UserKey, err := CSP.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{Temporary: true}) + assert.NoError(b, err) + + IssuerNonce := make([]byte, curve.ScalarByteSize) + n, err := rand.Read(IssuerNonce) + assert.NoError(b, err) + assert.Equal(b, curve.ScalarByteSize, n) + + blindCredReqOpts := &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce} + credRequest, err := CSP.Sign( + UserKey, + nil, + blindCredReqOpts, + ) + assert.NoError(b, err) + + credential, err := CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + }, + }, + ) + assert.NoError(b, err) + + cr := &aries.CredRequest{ + Curve: curve, + } + + credential, err = cr.Unblind(credential, blindCredReqOpts.Blinding) + assert.NoError(b, err) + + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixBlindCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + assert.NoError(b, err) + assert.True(b, valid) + + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + }, + }, + ) + assert.NoError(b, err) + assert.True(b, valid) + + NymKey, err := CSP.KeyDeriv(UserKey, &bccsp.IdemixNymKeyDerivationOpts{Temporary: true, IssuerPK: IssuerPublicKey}) + assert.NoError(b, err) + + return CSP, IssuerPublicKey, UserKey, NymKey, credential +} + +func setupLegacy(b *testing.B) (bccsp.BCCSP, bccsp.Key, bccsp.Key, bccsp.Key, []byte) { + curve := math.Curves[math.BLS12_381_BBS] + translator := &amcl.Gurvy{C: curve} + + CSP, err := idemix.New(NewDummyKeyStore(), curve, translator, true) + assert.NoError(b, err) + + AttributeNames := []string{imsp.AttributeNameOU, imsp.AttributeNameRole, imsp.AttributeNameEnrollmentId, imsp.AttributeNameRevocationHandle} + IssuerKey, err := CSP.KeyGen(&bccsp.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + assert.NoError(b, err) + IssuerPublicKey, err := IssuerKey.PublicKey() + assert.NoError(b, err) + + UserKey, err := CSP.KeyGen(&bccsp.IdemixUserSecretKeyGenOpts{Temporary: true}) + assert.NoError(b, err) + + IssuerNonce := make([]byte, curve.ScalarByteSize) + n, err := rand.Read(IssuerNonce) + assert.NoError(b, err) + assert.Equal(b, curve.ScalarByteSize, n) + + // Credential Request for User + credRequest, err := CSP.Sign( + UserKey, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce}, + ) + assert.NoError(b, err) + + // Credential + credential, err := CSP.Sign( + IssuerKey, + credRequest, + &bccsp.IdemixCredentialSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + }, + }, + ) + assert.NoError(b, err) + + valid, err := CSP.Verify( + IssuerPublicKey, + credRequest, + nil, + &bccsp.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + assert.NoError(b, err) + assert.True(b, valid) + + valid, err = CSP.Verify( + UserKey, + credential, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0}}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: bccsp.IdemixIntAttribute, Value: 1}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + }, + }, + ) + assert.NoError(b, err) + assert.True(b, valid) + + NymKey, err := CSP.KeyDeriv(UserKey, &bccsp.IdemixNymKeyDerivationOpts{Temporary: true, IssuerPK: IssuerPublicKey}) + assert.NoError(b, err) + + return CSP, IssuerPublicKey, UserKey, NymKey, credential +} + +func stackNameFromSetupFnName(fname string) string { + if strings.Contains(fname, "Aries") { + return "aries" + } + + if strings.Contains(fname, "Legacy") { + return "legacy" + } + + panic("programming error") +} + +func Benchmark_SignVerify(b *testing.B) { + + setups := []func(b *testing.B) (bccsp.BCCSP, bccsp.Key, bccsp.Key, bccsp.Key, []byte){ + setupLegacy, + setupAries, + } + + for _, setupFn := range setups { + + CSP, IssuerPublicKey, UserKey, NymKey, credential := setupFn(b) + + b.ResetTimer() + + b.Run(fmt.Sprintf("sign-%s", stackNameFromSetupFnName(runtime.FuncForPC(reflect.ValueOf(setupFn).Pointer()).Name())), func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + signOpts := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + Epoch: 0, + SigType: bccsp.EidNymRhNym, + } + + signature, err := CSP.Sign( + UserKey, + nil, + signOpts, + ) + assert.NoError(b, err) + + _ = signature + } + }) + }) + + RevocationKey, err := CSP.KeyGen(&bccsp.IdemixRevocationKeyGenOpts{Temporary: true}) + assert.NoError(b, err) + + cri, err := CSP.Sign( + RevocationKey, + nil, + &bccsp.IdemixCRISignerOpts{}, + ) + assert.NoError(b, err) + + signOpts := &bccsp.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + Epoch: 0, + SigType: bccsp.EidNymRhNym, + CRI: cri, + } + + signature, err := CSP.Sign( + UserKey, + nil, + signOpts, + ) + assert.NoError(b, err) + + b.ResetTimer() + + b.Run(fmt.Sprintf("verify-%s", stackNameFromSetupFnName(runtime.FuncForPC(reflect.ValueOf(setupFn).Pointer()).Name())), func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + valid, err := CSP.Verify( + IssuerPublicKey, + signature, + nil, + &bccsp.IdemixSignerOpts{ + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + Epoch: 0, + VerificationType: bccsp.ExpectEidNymRhNym, + Metadata: signOpts.Metadata, + }, + ) + assert.NoError(b, err) + assert.True(b, valid) + } + }) + }) + } +} diff --git a/v2/bccsp/schemes/dlog/bridge/bridge_suite_test.go b/v2/bccsp/schemes/dlog/bridge/bridge_suite_test.go new file mode 100644 index 0000000..5df0e9d --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/bridge_suite_test.go @@ -0,0 +1,24 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge_test + +import ( + "io" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPlain(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plain Suite") +} + +// NewRandPanic is an utility test function that always panic when invoked +func NewRandPanic() io.Reader { + panic("new rand panic") +} diff --git a/v2/bccsp/schemes/dlog/bridge/bridge_test.go b/v2/bccsp/schemes/dlog/bridge/bridge_test.go new file mode 100644 index 0000000..8d5e8e7 --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/bridge_test.go @@ -0,0 +1,1530 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge_test + +import ( + "crypto/rand" + "fmt" + "io" + + "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/bccsp/types/mock" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/bridge" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func rndOrPanic(curve *math.Curve) io.Reader { + rnd, err := curve.Rand() + if err != nil { + panic(err) + } + + return rnd +} + +var _ = Describe("Idemix Bridge", func() { + var ( + userSecretKey *math.Zr + issuerPublicKey types.IssuerPublicKey + issuerSecretKey types.IssuerSecretKey + nymPublicKey *math.G1 + nymSecretKey *math.Zr + ) + + BeforeEach(func() { + userSecretKey = math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + issuerPublicKey = &bridge.IssuerPublicKey{} + issuerSecretKey = &bridge.IssuerSecretKey{} + nymPublicKey = math.Curves[math.FP256BN_AMCL].GenG1 + nymSecretKey = math.Curves[math.FP256BN_AMCL].NewZrFromInt(0) + }) + + Describe("issuer", func() { + var ( + Issuer *bridge.Issuer + ) + + BeforeEach(func() { + Issuer = &bridge.Issuer{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("key generation", func() { + + Context("successful generation", func() { + var ( + key types.IssuerSecretKey + err error + attributes []string + ) + + It("with valid attributes", func() { + attributes = []string{"A", "B"} + key, err = Issuer.NewKey(attributes) + Expect(err).NotTo(HaveOccurred()) + Expect(key).NotTo(BeNil()) + }) + + It("with empty attributes", func() { + attributes = nil + key, err = Issuer.NewKey(attributes) + Expect(err).NotTo(HaveOccurred()) + Expect(key).NotTo(BeNil()) + }) + + AfterEach(func() { + raw, err := key.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + pk := key.Public() + Expect(pk).NotTo(BeNil()) + + h := pk.Hash() + Expect(h).NotTo(BeEmpty()) + + raw, err = pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + pk2, err := Issuer.NewPublicKeyFromBytes(raw, attributes) + Expect(err).NotTo(HaveOccurred()) + + raw2, err := pk2.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw2).NotTo(BeEmpty()) + Expect(pk2.Hash()).To(BeEquivalentTo(pk.Hash())) + Expect(raw2).To(BeEquivalentTo(raw)) + }) + }) + }) + + Context("public key import", func() { + + It("fails to unmarshal issuer public key", func() { + pk, err := Issuer.NewPublicKeyFromBytes([]byte{0, 1, 2, 3, 4}, nil) + Expect(err.Error()).To(ContainSubstring("failed to unmarshal issuer public key: proto")) + Expect(pk).To(BeNil()) + }) + + It("fails to unmarshal issuer public key", func() { + pk, err := Issuer.NewPublicKeyFromBytes(nil, nil) + Expect(err).To(MatchError(ContainSubstring("nil argument"))) + Expect(pk).To(BeNil()) + }) + + Context("and it is modified", func() { + var ( + pk types.IssuerPublicKey + ) + BeforeEach(func() { + attributes := []string{"A", "B"} + key, err := Issuer.NewKey(attributes) + Expect(err).NotTo(HaveOccurred()) + pk = key.Public() + Expect(pk).NotTo(BeNil()) + }) + + It("fails to validate invalid issuer public key", func() { + if pk.(*bridge.IssuerPublicKey).PK.ProofC[0] != 1 { + pk.(*bridge.IssuerPublicKey).PK.ProofC[0] = 1 + } else { + pk.(*bridge.IssuerPublicKey).PK.ProofC[0] = 0 + } + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + pk, err = Issuer.NewPublicKeyFromBytes(raw, nil) + Expect(err).To(MatchError("invalid issuer public key: zero knowledge proof in public key invalid")) + Expect(pk).To(BeNil()) + }) + + It("fails to verify attributes, different length", func() { + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + pk, err := Issuer.NewPublicKeyFromBytes(raw, []string{"A"}) + Expect(err).To(MatchError("invalid number of attributes, expected [2], got [1]")) + Expect(pk).To(BeNil()) + }) + + It("fails to verify attributes, different attributes", func() { + raw, err := pk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + pk, err := Issuer.NewPublicKeyFromBytes(raw, []string{"A", "C"}) + Expect(err).To(MatchError("invalid attribute name at position [1]")) + Expect(pk).To(BeNil()) + }) + }) + + }) + }) + + Describe("user", func() { + var ( + User *bridge.User + ) + + BeforeEach(func() { + User = &bridge.User{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("secret key import", func() { + It("success", func() { + key, err := User.NewKey() + Expect(err).NotTo(HaveOccurred()) + + raw := key.Bytes() + Expect(raw).NotTo(BeNil()) + + key2, err := User.NewKeyFromBytes(raw) + Expect(err).NotTo(HaveOccurred()) + + raw2 := key2.Bytes() + Expect(raw2).NotTo(BeNil()) + + Expect(raw2).To(BeEquivalentTo(raw)) + }) + + It("fails on nil raw", func() { + key, err := User.NewKeyFromBytes(nil) + Expect(err).To(MatchError("invalid length, expected [32], got [0]")) + Expect(key).To(BeNil()) + }) + + It("fails on invalid raw", func() { + key, err := User.NewKeyFromBytes([]byte{0, 1, 2, 3}) + Expect(err).To(MatchError("invalid length, expected [32], got [4]")) + Expect(key).To(BeNil()) + }) + }) + + Context("nym generation", func() { + + It("fails on nil issuer public key", func() { + r1, r2, err := User.MakeNym(userSecretKey, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + Expect(r1).To(BeNil()) + Expect(r2).To(BeNil()) + }) + + It("fails on invalid issuer public key", func() { + r1, r2, err := User.MakeNym(userSecretKey, &mock.IssuerPublicKey{}) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got [*mock.IssuerPublicKey]")) + Expect(r1).To(BeNil()) + Expect(r2).To(BeNil()) + }) + }) + + Context("public nym import", func() { + It("success", func() { + curve := math.Curves[math.FP256BN_AMCL] + rng, err := curve.Rand() + Expect(err).NotTo(HaveOccurred()) + + g := curve.GenG1 + r := curve.NewRandomZr(rng) + h := g.Mul(r) + + npk := handlers.NewNymPublicKey(h, &amcl.Fp256bn{C: curve}) + raw, err := npk.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeNil()) + + npk2, err := User.NewPublicNymFromBytes(raw) + Expect(err).NotTo(HaveOccurred()) + + Expect(npk2.Equals(h)).To(BeTrue()) + + raw2, err := handlers.NewNymPublicKey(npk2, &amcl.Fp256bn{C: curve}).Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw2).NotTo(BeNil()) + + Expect(raw2).To(BeEquivalentTo(raw)) + }) + + It("panic on nil raw", func() { + key, err := User.NewPublicNymFromBytes(nil) + Expect(err).To(MatchError("invalid marshalled length")) + Expect(key).To(BeNil()) + }) + + It("failure unmarshalling invalid raw", func() { + key, err := User.NewPublicNymFromBytes([]byte{0, 1, 2, 3}) + Expect(err).To(MatchError("invalid marshalled length")) + Expect(key).To(BeNil()) + }) + + }) + }) + + Describe("credential request", func() { + var ( + CredRequest *bridge.CredRequest + IssuerNonce []byte + ) + BeforeEach(func() { + CredRequest = &bridge.CredRequest{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + IssuerNonce = make([]byte, 32) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(32)) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("sign", func() { + It("fail on nil issuer public key", func() { + raw, err := CredRequest.Sign(userSecretKey, nil, IssuerNonce) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + Expect(raw).To(BeNil()) + }) + + It("fail on invalid issuer public key", func() { + raw, err := CredRequest.Sign(userSecretKey, &mock.IssuerPublicKey{}, IssuerNonce) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got [*mock.IssuerPublicKey]")) + Expect(raw).To(BeNil()) + }) + + It("fail on nil nonce", func() { + raw, err := CredRequest.Sign(userSecretKey, issuerPublicKey, nil) + Expect(err).To(MatchError("invalid issuer nonce, expected length 32, got 0")) + Expect(raw).To(BeNil()) + }) + + It("fail on empty nonce", func() { + raw, err := CredRequest.Sign(userSecretKey, issuerPublicKey, []byte{}) + Expect(err).To(MatchError("invalid issuer nonce, expected length 32, got 0")) + Expect(raw).To(BeNil()) + }) + }) + + Context("verify", func() { + It("panic on nil credential request", func() { + err := CredRequest.Verify(nil, issuerPublicKey, IssuerNonce) + Expect(err).To(MatchError(ContainSubstring("nil argument"))) + }) + + It("fail on invalid credential request", func() { + err := CredRequest.Verify([]byte{0, 1, 2, 3, 4}, issuerPublicKey, IssuerNonce) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + }) + + It("fail on nil issuer public key", func() { + err := CredRequest.Verify(nil, nil, IssuerNonce) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + }) + + It("fail on invalid issuer public key", func() { + err := CredRequest.Verify(nil, &mock.IssuerPublicKey{}, IssuerNonce) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got [*mock.IssuerPublicKey]")) + }) + + }) + }) + + Describe("credential", func() { + var ( + Credential types.Credential + ) + BeforeEach(func() { + Credential = &bridge.Credential{ + Idemix: &idemix.Idemix{ + Curve: math.Curves[math.FP256BN_AMCL], + }, + } + }) + + Context("sign", func() { + + It("fail on nil issuer secret key", func() { + raw, err := Credential.Sign(nil, []byte{0, 1, 2, 3, 4}, nil) + Expect(err).To(MatchError("invalid issuer secret key, expected *Big, got []")) + Expect(raw).To(BeNil()) + }) + + It("fail on invalid credential request", func() { + raw, err := Credential.Sign(issuerSecretKey, []byte{0, 1, 2, 3, 4}, nil) + Expect(err.Error()).To(ContainSubstring("failed unmarshalling credential request: proto")) + Expect(raw).To(BeNil()) + }) + + It("fail on nil inputs", func() { + raw, err := Credential.Sign(issuerSecretKey, nil, nil) + Expect(err).To(MatchError("failure [runtime error: invalid memory address or nil pointer dereference]")) + Expect(raw).To(BeNil()) + }) + + It("fail on invalid attributes", func() { + raw, err := Credential.Sign(issuerSecretKey, nil, []types.IdemixAttribute{ + {Type: 5, Value: nil}, + }) + Expect(err).To(MatchError("attribute type not allowed or supported [5] at position [0]")) + Expect(raw).To(BeNil()) + }) + }) + + Context("verify", func() { + It("fail on nil issuer public key", func() { + err := Credential.Verify(userSecretKey, nil, nil, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got [*math.Zr]")) + }) + + It("fail on invalid issuer public key", func() { + err := Credential.Verify(userSecretKey, &mock.IssuerPublicKey{}, nil, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got [*math.Zr]")) + }) + + It("fail on invalid attributes", func() { + err := Credential.Verify(userSecretKey, issuerPublicKey, nil, []types.IdemixAttribute{ + {Type: 5, Value: nil}, + }) + Expect(err).To(MatchError("attribute type not allowed or supported [5] at position [0]")) + }) + }) + }) + + Describe("revocation", func() { + var ( + Revocation types.Revocation + ) + BeforeEach(func() { + Revocation = &bridge.Revocation{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("sign", func() { + + It("fail on nil inputs", func() { + raw, err := Revocation.Sign(nil, nil, 0, 0) + Expect(err).To(MatchError("failed creating CRI: CreateCRI received nil input")) + Expect(raw).To(BeNil()) + }) + + It("fail on invalid handlers", func() { + raw, err := Revocation.Sign(nil, [][]byte{{0, 2, 3, 4}}, 0, 0) + Expect(err).To(MatchError(ContainSubstring("CreateCRI received nil input"))) + Expect(raw).To(BeNil()) + }) + }) + + Context("verify", func() { + It("fail on nil inputs", func() { + err := Revocation.Verify(nil, nil, 0, 0) + Expect(err).To(MatchError("EpochPK invalid: received nil input")) + }) + + It("fail on malformed cri", func() { + err := Revocation.Verify(nil, []byte{0, 1, 2, 3, 4}, 0, 0) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + }) + }) + }) + + Describe("signature", func() { + var ( + SignatureScheme types.SignatureScheme + ) + BeforeEach(func() { + SignatureScheme = &bridge.SignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("sign", func() { + It("fail on nil issuer public key", func() { + signature, _, err := SignatureScheme.Sign(nil, userSecretKey, nymPublicKey, nymSecretKey, nil, nil, nil, 0, 0, nil, 0, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + Expect(signature).To(BeNil()) + }) + }) + + Context("verify", func() { + It("fail on nil issuer Public key", func() { + err := SignatureScheme.Verify(nil, nil, nil, nil, 0, 2, 1, nil, 0, 0, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + }) + + It("fail on nil signature", func() { + err := SignatureScheme.Verify(issuerPublicKey, nil, nil, nil, 0, 2, 1, nil, 0, 0, nil) + Expect(err).To(MatchError("cannot verify idemix signature: received nil input")) + }) + + It("fail on invalid signature", func() { + err := SignatureScheme.Verify(issuerPublicKey, []byte{0, 1, 2, 3, 4}, nil, nil, 0, 2, 1, nil, 0, 0, nil) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + }) + + It("fail on invalid attributes", func() { + err := SignatureScheme.Verify(issuerPublicKey, nil, nil, + []types.IdemixAttribute{{Type: -1}}, 0, 2, 1, nil, 0, 0, nil) + Expect(err).To(MatchError("attribute type not allowed or supported [-1] at position [0]")) + }) + }) + }) + + Describe("nym signature", func() { + var ( + NymSignatureScheme types.NymSignatureScheme + ) + BeforeEach(func() { + NymSignatureScheme = &bridge.NymSignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + }) + + Context("sign", func() { + It("fail on nil issuer public key", func() { + signature, err := NymSignatureScheme.Sign(userSecretKey, nymPublicKey, nymSecretKey, nil, nil) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + Expect(signature).To(BeNil()) + }) + }) + + Context("verify", func() { + It("fail on nil issuer Public key", func() { + err := NymSignatureScheme.Verify(nil, nil, nil, nil, 0) + Expect(err).To(MatchError("invalid issuer public key, expected *IssuerPublicKey, got []")) + }) + + It("panic on nil signature", func() { + err := NymSignatureScheme.Verify(issuerPublicKey, nymPublicKey, nil, nil, 0) + Expect(err).To(MatchError(ContainSubstring("failure [runtime error: invalid memory address or nil pointer dereference]"))) + }) + + It("fail on invalid signature", func() { + err := NymSignatureScheme.Verify(issuerPublicKey, nymPublicKey, []byte{0, 1, 2, 3, 4}, nil, 0) + Expect(err.Error()).To(ContainSubstring("error unmarshalling signature")) + }) + + }) + }) + + Describe("setting up the environment with one issuer and one user", func() { + var ( + Issuer types.Issuer + IssuerKeyGen *handlers.IssuerKeyGen + IssuerKey types.Key + IssuerPublicKey types.Key + AttributeNames []string + + User types.User + UserKeyGen *handlers.UserKeyGen + UserKey types.Key + NymKeyDerivation *handlers.NymKeyDerivation + NymKey types.Key + NymPublicKey types.Key + + CredRequest types.CredRequest + CredentialRequestSigner *handlers.CredentialRequestSigner + CredentialRequestVerifier *handlers.CredentialRequestVerifier + IssuerNonce []byte + credRequest []byte + + Credential types.Credential + CredentialSigner *handlers.CredentialSigner + CredentialVerifier *handlers.CredentialVerifier + credential []byte + + Revocation types.Revocation + RevocationKeyGen *handlers.RevocationKeyGen + RevocationKey types.Key + RevocationPublicKey types.Key + CriSigner *handlers.CriSigner + CriVerifier *handlers.CriVerifier + cri []byte + ) + + BeforeEach(func() { + // Issuer + var err error + Issuer = &bridge.Issuer{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + IssuerKeyGen = &handlers.IssuerKeyGen{Issuer: Issuer} + AttributeNames = []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + IssuerKey, err = IssuerKeyGen.KeyGen(&types.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).NotTo(HaveOccurred()) + IssuerPublicKey, err = IssuerKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // User + User = &bridge.User{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + UserKeyGen = &handlers.UserKeyGen{User: User} + UserKey, err = UserKeyGen.KeyGen(&types.IdemixUserSecretKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + + // User Nym Key + NymKeyDerivation = &handlers.NymKeyDerivation{User: User, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + NymKey, err = NymKeyDerivation.KeyDeriv(UserKey, &types.IdemixNymKeyDerivationOpts{IssuerPK: IssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = NymKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // Credential Request for User + IssuerNonce = make([]byte, 32) + n, err := rand.Read(IssuerNonce) + Expect(n).To(BeEquivalentTo(32)) + Expect(err).NotTo(HaveOccurred()) + + CredRequest = &bridge.CredRequest{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + CredentialRequestSigner = &handlers.CredentialRequestSigner{CredRequest: CredRequest} + CredentialRequestVerifier = &handlers.CredentialRequestVerifier{CredRequest: CredRequest} + credRequest, err = CredentialRequestSigner.Sign( + UserKey, + nil, + &types.IdemixCredentialRequestSignerOpts{IssuerPK: IssuerPublicKey, IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + + // Credential + Credential = &bridge.Credential{ + Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}, + Idemix: &idemix.Idemix{ + Curve: math.Curves[math.FP256BN_AMCL], + }, + } + CredentialSigner = &handlers.CredentialSigner{Credential: Credential} + CredentialVerifier = &handlers.CredentialVerifier{Credential: Credential} + credential, err = CredentialSigner.Sign( + IssuerKey, + credRequest, + &types.IdemixCredentialSignerOpts{ + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + // Revocation + Revocation = &bridge.Revocation{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + RevocationKeyGen = &handlers.RevocationKeyGen{Revocation: Revocation} + RevocationKey, err = RevocationKeyGen.KeyGen(&types.IdemixRevocationKeyGenOpts{}) + Expect(err).NotTo(HaveOccurred()) + RevocationPublicKey, err = RevocationKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + // CRI + CriSigner = &handlers.CriSigner{Revocation: Revocation} + CriVerifier = &handlers.CriVerifier{Revocation: Revocation} + cri, err = CriSigner.Sign( + RevocationKey, + nil, + &types.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the environment is properly set", func() { + // Verify CredRequest + valid, err := CredentialRequestVerifier.Verify( + IssuerPublicKey, + credRequest, + nil, + &types.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify Credential + valid, err = CredentialVerifier.Verify( + UserKey, + credential, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + // Verify CRI + valid, err = CriVerifier.Verify( + RevocationPublicKey, + cri, + nil, + &types.IdemixCRISignerOpts{}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Context("the environment is not valid with the respect to different parameters", func() { + + It("invalid credential request nonce", func() { + valid, err := CredentialRequestVerifier.Verify( + IssuerPublicKey, + credRequest, + nil, + &types.IdemixCredentialRequestSignerOpts{IssuerNonce: []byte("pine-apple-pine-apple-pine-apple")}, + ) + Expect(err).To(MatchError(fmt.Sprintf("invalid nonce, expected [%v], got [%v]", []byte("pine-apple-pine-apple-pine-apple"), IssuerNonce))) + Expect(valid).NotTo(BeTrue()) + }) + + It("invalid credential request nonce, too short", func() { + valid, err := CredentialRequestVerifier.Verify( + IssuerPublicKey, + credRequest, + nil, + &types.IdemixCredentialRequestSignerOpts{IssuerNonce: []byte("pin-apple-pine-apple-pineapple")}, + ) + Expect(err).To(MatchError("invalid issuer nonce, expected length 32, got 30")) + Expect(valid).NotTo(BeTrue()) + }) + + It("invalid credential request", func() { + if credRequest[4] == 0 { + credRequest[4] = 1 + } else { + credRequest[4] = 0 + } + valid, err := CredentialRequestVerifier.Verify( + IssuerPublicKey, + credRequest, + nil, + &types.IdemixCredentialRequestSignerOpts{IssuerNonce: IssuerNonce}, + ) + Expect(err).To(MatchError("zero knowledge proof is invalid")) + Expect(valid).NotTo(BeTrue()) + }) + + It("invalid credential request in verifying credential", func() { + if credRequest[4] == 0 { + credRequest[4] = 1 + } else { + credRequest[4] = 0 + } + credential, err := CredentialSigner.Sign( + IssuerKey, + credRequest, + &types.IdemixCredentialSignerOpts{ + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2, 3}}, + }, + }, + ) + Expect(err).To(MatchError("failed creating new credential: zero knowledge proof is invalid")) + Expect(credential).To(BeNil()) + }) + + It("nil credential", func() { + // Verify Credential + valid, err := CredentialVerifier.Verify( + UserKey, + nil, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err).To(MatchError("invalid signature, it must not be empty")) + Expect(valid).To(BeFalse()) + }) + + It("malformed credential", func() { + // Verify Credential + valid, err := CredentialVerifier.Verify( + UserKey, + []byte{0, 1, 2, 3, 4}, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + Expect(valid).To(BeFalse()) + }) + + It("invalid credential", func() { + // Invalidate credential by changing it in one position + if credential[4] == 0 { + credential[4] = 1 + } else { + credential[4] = 0 + } + + // Verify Credential + valid, err := CredentialVerifier.Verify( + UserKey, + credential, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{2, 1, 0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err).To(MatchError("credential is not cryptographically valid")) + Expect(valid).To(BeFalse()) + }) + + It("invalid byte array in credential", func() { + // Verify Credential + valid, err := CredentialVerifier.Verify( + UserKey, + credential, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{1}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixIntAttribute, Value: 1}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err).To(MatchError("credential does not contain the correct attribute value at position [0]")) + Expect(valid).To(BeFalse()) + }) + + It("invalid int in credential", func() { + // Verify Credential + valid, err := CredentialVerifier.Verify( + UserKey, + credential, + nil, + &types.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1}}, + {Type: types.IdemixIntAttribute, Value: 2}, + {Type: types.IdemixBytesAttribute, Value: []byte{0, 1, 2}}, + {Type: types.IdemixHiddenAttribute}, + }, + }, + ) + Expect(err).To(MatchError("credential does not contain the correct attribute value at position [2]")) + Expect(valid).To(BeFalse()) + + }) + + It("invalid cri", func() { + // Verify CRI + cri[8] = 0 + valid, err := CriVerifier.Verify( + RevocationPublicKey, + cri, + nil, + &types.IdemixCRISignerOpts{}, + ) + Expect(err).To(MatchError("EpochPKSig invalid")) + Expect(valid).To(BeFalse()) + }) + }) + + Describe("the environment is not properly set", func() { + + Describe("issuer", func() { + Context("duplicate attribute", func() { + It("returns an error", func() { + AttributeNames = []string{"A", "A"} + IssuerKey, err := IssuerKeyGen.KeyGen(&types.IdemixIssuerKeyGenOpts{Temporary: true, AttributeNames: AttributeNames}) + Expect(err).To(MatchError("attribute A appears multiple times in AttributeNames")) + Expect(IssuerKey).To(BeNil()) + }) + }) + }) + + }) + + Describe("producing a signature with a nym eid", func() { + var ( + SignatureScheme types.SignatureScheme + Signer *handlers.Signer + Verifier *handlers.Verifier + + digest []byte + SignerOpts *types.IdemixSignerOpts + signature []byte + ) + + BeforeEach(func() { + SignatureScheme = &bridge.SignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + Signer = &handlers.Signer{SignatureScheme: SignatureScheme} + Verifier = &handlers.Verifier{SignatureScheme: SignatureScheme} + + digest = []byte("a digest") + SignerOpts = &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 2, + EidIndex: 3, + SigType: types.EidNym, + } + + var err error + signature, err = Signer.Sign(UserKey, digest, SignerOpts) + Expect(err).NotTo(HaveOccurred()) + }) + + It("nym eid audit succeed", func() { + valid, err := Verifier.AuditNymEid(IssuerPublicKey, signature, digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + AuditVerificationType: types.AuditExpectSignature, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = Verifier.AuditNymEid(IssuerPublicKey, SignerOpts.Metadata.EidNymAuditData.Nym.Bytes(), digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + AuditVerificationType: types.AuditExpectEidNym, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + + valid, err = Verifier.AuditNymEid(IssuerPublicKey, SignerOpts.Metadata.EidNymAuditData.Nym.Bytes(), digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + AuditVerificationType: types.AuditExpectEidNymRhNym, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + It("fails because it gets the wrong type of signature", func() { + valid, err := Verifier.AuditNymEid(IssuerPublicKey, []byte("To ride the storm, to an empire of the clouds"), digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + }) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + Expect(valid).To(BeFalse()) + }) + }) + + Describe("producing a signature with a nym eid and a nym rh", func() { + var ( + SignatureScheme types.SignatureScheme + Signer *handlers.Signer + Verifier *handlers.Verifier + + digest []byte + SignerOpts *types.IdemixSignerOpts + signature []byte + ) + + BeforeEach(func() { + SignatureScheme = &bridge.SignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + Signer = &handlers.Signer{SignatureScheme: SignatureScheme} + Verifier = &handlers.Verifier{SignatureScheme: SignatureScheme} + + digest = []byte("a digest") + SignerOpts = &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 2, + EidIndex: 3, + SigType: types.EidNymRhNym, + } + + var err error + signature, err = Signer.Sign(UserKey, digest, SignerOpts) + Expect(err).NotTo(HaveOccurred()) + }) + + It("nym eid and rh audit succeed", func() { + validNymEid, err := Verifier.AuditNymEid(IssuerPublicKey, signature, digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(validNymEid).To(BeTrue()) + + validNymRh, err := Verifier.AuditNymRh(IssuerPublicKey, signature, digest, &types.RhNymAuditOpts{ + RhIndex: 2, + RNymRh: SignerOpts.Metadata.RhNymAuditData.Rand, + RevocationHandle: string([]byte{2, 1, 0}), + AuditVerificationType: types.AuditExpectSignature, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(validNymRh).To(BeTrue()) + + validNymRh, err = Verifier.AuditNymRh(IssuerPublicKey, SignerOpts.Metadata.RhNymAuditData.Nym.Bytes(), digest, &types.RhNymAuditOpts{ + RhIndex: 2, + RNymRh: SignerOpts.Metadata.RhNymAuditData.Rand, + RevocationHandle: string([]byte{2, 1, 0}), + AuditVerificationType: types.AuditExpectEidNymRhNym, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(validNymRh).To(BeTrue()) + + _, err = Verifier.AuditNymRh(IssuerPublicKey, SignerOpts.Metadata.RhNymAuditData.Nym.Bytes(), digest, &types.RhNymAuditOpts{ + RhIndex: 2, + RNymRh: SignerOpts.Metadata.RhNymAuditData.Rand, + RevocationHandle: string([]byte{2, 1, 0}), + AuditVerificationType: types.AuditExpectEidNym, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid audit type [1]")) + }) + + It("fails because it gets the wrong type of signature", func() { + validNymEid, err := Verifier.AuditNymEid(IssuerPublicKey, []byte("To ride the storm, to an empire of the clouds"), digest, &types.EidNymAuditOpts{ + EidIndex: 3, + RNymEid: SignerOpts.Metadata.EidNymAuditData.Rand, + EnrollmentID: string([]byte{0, 1, 2}), + }) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + Expect(validNymEid).To(BeFalse()) + + validNymRh, err := Verifier.AuditNymRh(IssuerPublicKey, []byte("To ride the storm, to an empire of the clouds"), digest, &types.RhNymAuditOpts{ + RhIndex: 2, + RNymRh: SignerOpts.Metadata.RhNymAuditData.Rand, + RevocationHandle: string([]byte{2, 1, 0}), + }) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + Expect(validNymRh).To(BeFalse()) + }) + }) + + Describe("producing and verifying idemix signature with different sets of attributes", func() { + var ( + SignatureScheme types.SignatureScheme + Signer *handlers.Signer + Verifier *handlers.Verifier + digest []byte + signature []byte + + SignAttributes []types.IdemixAttribute + VerifyAttributes []types.IdemixAttribute + RhIndex int + Epoch int + errMessage string + validity bool + ) + + BeforeEach(func() { + SignatureScheme = &bridge.SignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + Signer = &handlers.Signer{SignatureScheme: SignatureScheme} + Verifier = &handlers.Verifier{SignatureScheme: SignatureScheme} + + digest = []byte("a digest") + RhIndex = 4 + Epoch = 0 + errMessage = "" + }) + + It("the signature with no disclosed attributes is valid", func() { + validity = true + SignAttributes = []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + VerifyAttributes = SignAttributes + }) + + It("the signature with disclosed attributes is valid", func() { + validity = true + SignAttributes = []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + VerifyAttributes = SignAttributes + }) + + It("the signature with different disclosed attributes is not valid", func() { + validity = false + errMessage = "signature invalid: zero-knowledge proof is invalid" + SignAttributes = []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixIntAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + VerifyAttributes = []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{1}}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixIntAttribute, Value: 1}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + }) + + It("the signature with different disclosed attributes is not valid", func() { + validity = false + errMessage = "signature invalid: zero-knowledge proof is invalid" + SignAttributes = []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixIntAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + VerifyAttributes = []types.IdemixAttribute{ + {Type: types.IdemixBytesAttribute, Value: []byte{0}}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixIntAttribute, Value: 10}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + }) + + AfterEach(func() { + var err error + signature, err = Signer.Sign( + UserKey, + digest, + &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: RhIndex, + EidIndex: 2, + Epoch: Epoch, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + + valid, err := Verifier.Verify( + IssuerPublicKey, + signature, + digest, + &types.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: VerifyAttributes, + RhIndex: RhIndex, + EidIndex: 2, + Epoch: Epoch, + }, + ) + + if errMessage == "" { + Expect(err).NotTo(HaveOccurred()) + } else { + Expect(err).To(MatchError(errMessage)) + } + Expect(valid).To(BeEquivalentTo(validity)) + }) + + }) + + Context("producing an idemix signature", func() { + var ( + SignatureScheme types.SignatureScheme + Signer *handlers.Signer + SignAttributes []types.IdemixAttribute + Verifier *handlers.Verifier + ) + + BeforeEach(func() { + SignatureScheme = &bridge.SignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + Signer = &handlers.Signer{SignatureScheme: SignatureScheme} + SignAttributes = []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + } + Verifier = &handlers.Verifier{SignatureScheme: SignatureScheme} + }) + + It("fails when the credential is malformed", func() { + signature, err := Signer.Sign( + UserKey, + []byte("a message"), + &types.IdemixSignerOpts{ + Credential: []byte{0, 1, 2, 3}, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err.Error()).To(ContainSubstring("cannot parse invalid wire-format data")) + Expect(signature).To(BeNil()) + }) + + It("fails when the cri is malformed", func() { + signature, err := Signer.Sign( + UserKey, + []byte("a message"), + &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: []byte{0, 1, 2, 3, 4}, + }, + ) + Expect(err.Error()).To(ContainSubstring("failed unmarshalling credential revocation information: proto")) + Expect(signature).To(BeNil()) + }) + + It("fails when invalid rhIndex is passed", func() { + signature, err := Signer.Sign( + UserKey, + []byte("a message"), + &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: 5, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).To(MatchError("failed creating new signature: cannot create idemix signature: received invalid input")) + Expect(signature).To(BeNil()) + }) + + It("fails when the credential is invalid", func() { + if credential[4] != 0 { + credential[4] = 0 + } else { + credential[4] = 1 + } + + signature, err := Signer.Sign( + UserKey, + []byte("a message"), + &types.IdemixSignerOpts{ + Credential: credential, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(signature).NotTo(BeNil()) + + valid, err := Verifier.Verify( + IssuerPublicKey, + signature, + []byte("a message"), + &types.IdemixSignerOpts{ + RevocationPublicKey: RevocationPublicKey, + Attributes: SignAttributes, + RhIndex: 0, + EidIndex: 2, + Epoch: 0, + }, + ) + Expect(err).To(MatchError("signature invalid: APrime = 1")) + Expect(valid).To(BeFalse()) + + }) + + It("fails when the credential is nil", func() { + credential[4] = 0 + signature, err := Signer.Sign( + UserKey, + []byte("a message"), + &types.IdemixSignerOpts{ + Credential: nil, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: SignAttributes, + RhIndex: 4, + EidIndex: 2, + Epoch: 0, + CRI: cri, + }, + ) + Expect(err).To(MatchError(ContainSubstring("nil argument"))) + Expect(signature).To(BeNil()) + }) + }) + + Describe("producing an idemix nym signature", func() { + var ( + NymSignatureScheme *bridge.NymSignatureScheme + NymSigner *handlers.NymSigner + NymVerifier *handlers.NymVerifier + digest []byte + signature []byte + ) + + BeforeEach(func() { + var err error + NymSignatureScheme = &bridge.NymSignatureScheme{Idemix: &idemix.Idemix{Curve: math.Curves[math.FP256BN_AMCL]}, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + NymSigner = &handlers.NymSigner{NymSignatureScheme: NymSignatureScheme} + NymVerifier = &handlers.NymVerifier{NymSignatureScheme: NymSignatureScheme} + + digest = []byte("a digest") + + signature, err = NymSigner.Sign( + UserKey, + digest, + &types.IdemixNymSignerOpts{ + Nym: NymKey, + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + It("the signature is valid", func() { + valid, err := NymVerifier.Verify( + NymPublicKey, + signature, + digest, + &types.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(valid).To(BeTrue()) + }) + + Context("the signature is malformed", func() { + var nymSignature *idemix.NymSignature + + BeforeEach(func() { + nymSignature = &idemix.NymSignature{} + err := proto.Unmarshal(signature, nymSignature) + Expect(err).NotTo(HaveOccurred()) + }) + + marshalAndVerify := func(nymSignature *idemix.NymSignature) (bool, error) { + signature, err := proto.Marshal(nymSignature) + Expect(err).NotTo(HaveOccurred()) + + return NymVerifier.Verify( + NymPublicKey, + signature, + digest, + &types.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + }, + ) + } + + Context("cause nonce does not encode a proper Big", func() { + It("returns an error", func() { + nymSignature.Nonce = []byte{0, 1, 2, 3, 4} + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause nonce is nil", func() { + It("returns an error", func() { + nymSignature.Nonce = nil + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause nonce encode a different thing", func() { + It("returns an error", func() { + var err error + nymSignature.Nonce = math.Curves[math.FP256BN_AMCL].NewZrFromInt(1).Bytes() + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError("pseudonym signature invalid: zero-knowledge proof is invalid")) + }) + }) + + Context("cause ProofC is not encoded properly", func() { + It("returns an error", func() { + nymSignature.ProofC = []byte{0, 1, 2, 3, 4} + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofC is nil", func() { + It("returns an error", func() { + nymSignature.ProofC = nil + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofC encode a different thing", func() { + It("returns an error", func() { + var err error + nymSignature.Nonce = math.Curves[math.FP256BN_AMCL].NewZrFromInt(1).Bytes() + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError("pseudonym signature invalid: zero-knowledge proof is invalid")) + }) + }) + + Context("cause ProofSRNym is not encoded properly", func() { + It("returns an error", func() { + nymSignature.ProofSRNym = []byte{0, 1, 2, 3, 4} + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofSRNym is nil", func() { + It("returns an error", func() { + nymSignature.ProofSRNym = nil + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofSRNym encode a different thing", func() { + It("returns an error", func() { + var err error + nymSignature.Nonce = math.Curves[math.FP256BN_AMCL].NewZrFromInt(1).Bytes() + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError("pseudonym signature invalid: zero-knowledge proof is invalid")) + }) + }) + + Context("cause ProofSSk is not encoded properly", func() { + It("returns an error", func() { + nymSignature.ProofSSk = []byte{0, 1, 2, 3, 4} + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofSSk is nil", func() { + It("returns an error", func() { + nymSignature.ProofSSk = nil + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError(ContainSubstring("pseudonym signature invalid: zero-knowledge proof is invalid"))) + }) + }) + + Context("cause ProofSSk encode a different thing", func() { + It("returns an error", func() { + var err error + nymSignature.Nonce = math.Curves[math.FP256BN_AMCL].NewZrFromInt(1).Bytes() + + valid, err := marshalAndVerify(nymSignature) + Expect(valid).To(BeFalse()) + Expect(err).To(MatchError("pseudonym signature invalid: zero-knowledge proof is invalid")) + }) + }) + }) + }) + + Context("importing nym key", func() { + var ( + NymPublicKeyImporter *handlers.NymPublicKeyImporter + ) + + BeforeEach(func() { + NymPublicKeyImporter = &handlers.NymPublicKeyImporter{User: User, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + }) + + It("nym key import is successful", func() { + // User Nym Key + NymKeyDerivation = &handlers.NymKeyDerivation{User: User, Translator: &amcl.Fp256bn{C: math.Curves[math.FP256BN_AMCL]}} + NymKey, err := NymKeyDerivation.KeyDeriv(UserKey, &types.IdemixNymKeyDerivationOpts{IssuerPK: IssuerPublicKey}) + Expect(err).NotTo(HaveOccurred()) + NymPublicKey, err = NymKey.PublicKey() + Expect(err).NotTo(HaveOccurred()) + + raw, err := NymPublicKey.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw).NotTo(BeEmpty()) + + k, err := NymPublicKeyImporter.KeyImport(raw, nil) + Expect(err).NotTo(HaveOccurred()) + raw2, err := k.Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(raw2).To(BeEquivalentTo(raw)) + }) + }) + }) +}) diff --git a/v2/bccsp/schemes/dlog/bridge/credential.go b/v2/bccsp/schemes/dlog/bridge/credential.go new file mode 100644 index 0000000..6cfe27f --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/credential.go @@ -0,0 +1,130 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "bytes" + + "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// Credential encapsulates the idemix algorithms to produce (sign) a credential +// and verify it. Recall that a credential is produced by the Issuer upon a credential request, +// and it is verified by the requester. +type Credential struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// Sign produces an idemix credential. It takes in input the issuer secret key, +// a serialised credential request, and a list of attribute values. +// Notice that attributes should not contain attributes whose type is IdemixHiddenAttribute +// cause the credential needs to carry all the attribute values. +func (c *Credential) Sign(key types.IssuerSecretKey, credentialRequest []byte, attributes []types.IdemixAttribute) (res []byte, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + iisk, ok := key.(*IssuerSecretKey) + if !ok { + return nil, errors.Errorf("invalid issuer secret key, expected *Big, got [%T]", key) + } + + cr := &idemix.CredRequest{} + err = proto.Unmarshal(credentialRequest, cr) + if err != nil { + return nil, errors.Wrap(err, "failed unmarshalling credential request") + } + + attrValues := make([]*math.Zr, len(attributes)) + for i := 0; i < len(attributes); i++ { + switch attributes[i].Type { + case types.IdemixBytesAttribute: + attrValues[i] = c.Idemix.Curve.HashToZr(attributes[i].Value.([]byte)) + case types.IdemixIntAttribute: + var value int64 + if v, ok := attributes[i].Value.(int); ok { + value = int64(v) + } else if v, ok := attributes[i].Value.(int64); ok { + value = v + } else { + return nil, errors.Errorf("invalid int type for IdemixIntAttribute attribute") + } + attrValues[i] = c.Idemix.Curve.NewZrFromInt(value) + default: + return nil, errors.Errorf("attribute type not allowed or supported [%v] at position [%d]", attributes[i].Type, i) + } + } + + cred, err := c.Idemix.NewCredential(iisk.SK, cr, attrValues, newRandOrPanic(c.Idemix.Curve), c.Translator) + if err != nil { + return nil, errors.WithMessage(err, "failed creating new credential") + } + + return proto.Marshal(cred) +} + +// Verify checks that an idemix credential is cryptographically correct. It takes +// in input the user secret key (sk), the issuer public key (ipk), the serialised credential (credential), +// and a list of attributes. The list of attributes is optional, in case it is specified, Verify +// checks that the credential carries the specified attributes. +func (c *Credential) Verify(sk *math.Zr, ipk types.IssuerPublicKey, credential []byte, attributes []types.IdemixAttribute) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", sk) + } + + cred := &idemix.Credential{} + err = proto.Unmarshal(credential, cred) + if err != nil { + return err + } + + for i := 0; i < len(attributes); i++ { + switch attributes[i].Type { + case types.IdemixBytesAttribute: + if !bytes.Equal( + c.Idemix.Curve.HashToZr(attributes[i].Value.([]byte)).Bytes(), + cred.Attrs[i]) { + return errors.Errorf("credential does not contain the correct attribute value at position [%d]", i) + } + case types.IdemixIntAttribute: + var value int64 + if v, ok := attributes[i].Value.(int); ok { + value = int64(v) + } else if v, ok := attributes[i].Value.(int64); ok { + value = v + } else { + return errors.Errorf("invalid int type for IdemixIntAttribute attribute") + } + + if !bytes.Equal( + c.Idemix.Curve.NewZrFromInt(value).Bytes(), + cred.Attrs[i]) { + return errors.Errorf("credential does not contain the correct attribute value at position [%d]", i) + } + case types.IdemixHiddenAttribute: + continue + default: + return errors.Errorf("attribute type not allowed or supported [%v] at position [%d]", attributes[i].Type, i) + } + } + + return cred.Ver(sk, iipk.PK, c.Idemix.Curve, c.Translator) +} diff --git a/v2/bccsp/schemes/dlog/bridge/credrequest.go b/v2/bccsp/schemes/dlog/bridge/credrequest.go new file mode 100644 index 0000000..66167f3 --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/credrequest.go @@ -0,0 +1,92 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "bytes" + + "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// CredRequest encapsulates the idemix algorithms to produce (sign) a credential request +// and verify it. Recall that a credential request is produced by a user, +// and it is verified by the issuer at credential creation time. +type CredRequest struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// Sign produces an idemix credential request. It takes in input a user secret key and +// an issuer public key. +func (cr *CredRequest) Sign(sk *math.Zr, ipk types.IssuerPublicKey, nonce []byte) (res []byte, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return nil, errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + if len(nonce) != cr.Idemix.Curve.ScalarByteSize { + return nil, errors.Errorf("invalid issuer nonce, expected length %d, got %d", cr.Idemix.Curve.ScalarByteSize, len(nonce)) + } + + credRequest, err := cr.Idemix.NewCredRequest( + sk, + nonce, + iipk.PK, + newRandOrPanic(cr.Idemix.Curve), + cr.Translator, + ) + if err != nil { + return nil, err + } + + return proto.Marshal(credRequest) +} + +// Verify checks that the passed credential request is valid with the respect to the passed +// issuer public key. +func (cr *CredRequest) Verify(credentialRequest []byte, ipk types.IssuerPublicKey, nonce []byte) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + credRequest := &idemix.CredRequest{} + err = proto.Unmarshal(credentialRequest, credRequest) + if err != nil { + return err + } + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + err = credRequest.Check(iipk.PK, cr.Idemix.Curve, cr.Translator) + if err != nil { + return err + } + + // Nonce checks + if len(nonce) != cr.Idemix.Curve.ScalarByteSize { + return errors.Errorf("invalid issuer nonce, expected length %d, got %d", cr.Idemix.Curve.ScalarByteSize, len(nonce)) + } + if !bytes.Equal(nonce, credRequest.IssuerNonce) { + return errors.Errorf("invalid nonce, expected [%v], got [%v]", nonce, credRequest.IssuerNonce) + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/bridge/issuer.go b/v2/bccsp/schemes/dlog/bridge/issuer.go new file mode 100644 index 0000000..2ea217b --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/issuer.go @@ -0,0 +1,143 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "fmt" + + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// IssuerPublicKey encapsulate an idemix issuer public key. +type IssuerPublicKey struct { + PK *idemix.IssuerPublicKey +} + +func (o *IssuerPublicKey) Bytes() ([]byte, error) { + return proto.Marshal(o.PK) +} + +func (o *IssuerPublicKey) Hash() []byte { + return o.PK.Hash +} + +// IssuerPublicKey encapsulate an idemix issuer secret key. +type IssuerSecretKey struct { + SK *idemix.IssuerKey +} + +func (o *IssuerSecretKey) Bytes() ([]byte, error) { + return proto.Marshal(o.SK) +} + +func (o *IssuerSecretKey) Public() types.IssuerPublicKey { + return &IssuerPublicKey{o.SK.Ipk} +} + +// Issuer encapsulates the idemix algorithms to generate issuer key-pairs +type Issuer struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// NewKey generates a new issuer key-pair +func (i *Issuer) NewKey(attributeNames []string) (res types.IssuerSecretKey, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + sk, err := i.Idemix.NewIssuerKey(attributeNames, newRandOrPanic(i.Idemix.Curve), i.Translator) + if err != nil { + return + } + + res = &IssuerSecretKey{SK: sk} + + return +} + +func (i *Issuer) NewKeyFromBytes(raw []byte, attributes []string) (res types.IssuerSecretKey, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + sk, err := i.Idemix.NewIssuerKeyFromBytes(raw) + if err != nil { + return + } + + res = &IssuerSecretKey{SK: sk} + + return +} + +func (i *Issuer) NewPublicKeyFromBytes(raw []byte, attributes []string) (res types.IssuerPublicKey, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + ipk := new(idemix.IssuerPublicKey) + err = proto.Unmarshal(raw, ipk) + if err != nil { + return nil, errors.WithStack(&bccsp.IdemixIssuerPublicKeyImporterError{ + Type: bccsp.IdemixIssuerPublicKeyImporterUnmarshallingError, + ErrorMsg: "failed to unmarshal issuer public key", + Cause: err}) + } + + err = ipk.SetHash(i.Idemix.Curve) + if err != nil { + return nil, errors.WithStack(&bccsp.IdemixIssuerPublicKeyImporterError{ + Type: bccsp.IdemixIssuerPublicKeyImporterHashError, + ErrorMsg: "setting the hash of the issuer public key failed", + Cause: err}) + } + + err = ipk.Check(i.Idemix.Curve, i.Translator) + if err != nil { + return nil, errors.WithStack(&bccsp.IdemixIssuerPublicKeyImporterError{ + Type: bccsp.IdemixIssuerPublicKeyImporterValidationError, + ErrorMsg: "invalid issuer public key", + Cause: err}) + } + + if len(attributes) != 0 { + // Check the attributes + if len(attributes) != len(ipk.AttributeNames) { + return nil, errors.WithStack(&bccsp.IdemixIssuerPublicKeyImporterError{ + Type: bccsp.IdemixIssuerPublicKeyImporterNumAttributesError, + ErrorMsg: fmt.Sprintf("invalid number of attributes, expected [%d], got [%d]", + len(ipk.AttributeNames), len(attributes)), + }) + } + + for i, attr := range attributes { + if ipk.AttributeNames[i] != attr { + return nil, errors.WithStack(&bccsp.IdemixIssuerPublicKeyImporterError{ + Type: bccsp.IdemixIssuerPublicKeyImporterAttributeNameError, + ErrorMsg: fmt.Sprintf("invalid attribute name at position [%d]", i), + }) + } + } + } + + res = &IssuerPublicKey{PK: ipk} + + return +} diff --git a/v2/bccsp/schemes/dlog/bridge/nymsignaturescheme.go b/v2/bccsp/schemes/dlog/bridge/nymsignaturescheme.go new file mode 100644 index 0000000..cde67cc --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/nymsignaturescheme.go @@ -0,0 +1,74 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// NymSignatureScheme encapsulates the idemix algorithms to sign and verify using an idemix +// pseudonym. +type NymSignatureScheme struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// Sign produces a signature over the passed digest. It takes in input, the user secret key (sk), +// the pseudonym public key (Nym) and secret key (RNym), and the issuer public key (ipk). +func (n *NymSignatureScheme) Sign(sk *math.Zr, Nym *math.G1, RNym *math.Zr, ipk types.IssuerPublicKey, digest []byte) (res []byte, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return nil, errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + sig, err := n.Idemix.NewNymSignature( + sk, + Nym, + RNym, + iipk.PK, + digest, + newRandOrPanic(n.Idemix.Curve), + n.Translator) + if err != nil { + return nil, errors.WithMessage(err, "failed creating new nym signature") + } + + return proto.Marshal(sig) +} + +// Verify checks that the passed signatures is valid with the respect to the passed digest, issuer public key, +// and pseudonym public key. +func (n *NymSignatureScheme) Verify(ipk types.IssuerPublicKey, Nym *math.G1, signature, digest []byte, _ int) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + sig := &idemix.NymSignature{} + err = proto.Unmarshal(signature, sig) + if err != nil { + return errors.Wrap(err, "error unmarshalling signature") + } + + return sig.Ver(Nym, iipk.PK, digest, n.Idemix.Curve, n.Translator) +} diff --git a/v2/bccsp/schemes/dlog/bridge/rand.go b/v2/bccsp/schemes/dlog/bridge/rand.go new file mode 100644 index 0000000..efdd3d2 --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/rand.go @@ -0,0 +1,20 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "io" + + math "github.com/IBM/mathlib" +) + +func newRandOrPanic(curve *math.Curve) io.Reader { + rng, err := curve.Rand() + if err != nil { + panic(err) + } + return rng +} diff --git a/v2/bccsp/schemes/dlog/bridge/revocation.go b/v2/bccsp/schemes/dlog/bridge/revocation.go new file mode 100644 index 0000000..329401d --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/revocation.go @@ -0,0 +1,76 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "crypto/ecdsa" + + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// Revocation encapsulates the idemix algorithms for revocation +type Revocation struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// NewKey generate a new revocation key-pair. +func (r *Revocation) NewKey() (*ecdsa.PrivateKey, error) { + return r.Idemix.GenerateLongTermRevocationKey() +} + +func (r *Revocation) NewKeyFromBytes(raw []byte) (*ecdsa.PrivateKey, error) { + return r.Idemix.LongTermRevocationKeyFromBytes(raw) +} + +// Sign generates a new CRI with the respect to the passed unrevoked handles, epoch, and revocation algorithm. +func (r *Revocation) Sign(key *ecdsa.PrivateKey, unrevokedHandles [][]byte, epoch int, alg bccsp.RevocationAlgorithm) (res []byte, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + handles := make([]*math.Zr, len(unrevokedHandles)) + for i := 0; i < len(unrevokedHandles); i++ { + handles[i] = r.Idemix.Curve.NewZrFromBytes(unrevokedHandles[i]) + } + cri, err := r.Idemix.CreateCRI(key, handles, epoch, idemix.RevocationAlgorithm(alg), newRandOrPanic(r.Idemix.Curve), r.Translator) + if err != nil { + return nil, errors.WithMessage(err, "failed creating CRI") + } + + return proto.Marshal(cri) +} + +// Verify checks that the passed serialised CRI (criRaw) is valid with the respect to the passed revocation public key, +// epoch, and revocation algorithm. +func (r *Revocation) Verify(pk *ecdsa.PublicKey, criRaw []byte, epoch int, alg bccsp.RevocationAlgorithm) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + cri := &idemix.CredentialRevocationInformation{} + err = proto.Unmarshal(criRaw, cri) + if err != nil { + return err + } + + return r.Idemix.VerifyEpochPK( + pk, + cri.EpochPk, + cri.EpochPkSig, + int(cri.Epoch), + idemix.RevocationAlgorithm(cri.RevocationAlg), + ) +} diff --git a/v2/bccsp/schemes/dlog/bridge/signaturescheme.go b/v2/bccsp/schemes/dlog/bridge/signaturescheme.go new file mode 100644 index 0000000..fe236aa --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/signaturescheme.go @@ -0,0 +1,274 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bridge + +import ( + "crypto/ecdsa" + + "github.com/IBM/idemix/bccsp/types" + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// SignatureScheme encapsulates the idemix algorithms to sign and verify using an idemix credential. +type SignatureScheme struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// Sign produces an idemix-signature with the respect to the passed serialised credential (cred), +// user secret key (sk), pseudonym public key (Nym) and secret key (RNym), issuer public key (ipk), +// and attributes to be disclosed. +func (s *SignatureScheme) Sign(cred []byte, sk *math.Zr, Nym *math.G1, RNym *math.Zr, ipk types.IssuerPublicKey, attributes []bccsp.IdemixAttribute, + msg []byte, rhIndex, eidIndex int, criRaw []byte, sigType bccsp.SignatureType, metadata *bccsp.IdemixSignerMetadata) (res []byte, meta *bccsp.IdemixSignerMetadata, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return nil, nil, errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + credential := &idemix.Credential{} + err = proto.Unmarshal(cred, credential) + if err != nil { + return nil, nil, errors.Wrap(err, "failed unmarshalling credential") + } + + cri := &idemix.CredentialRevocationInformation{} + err = proto.Unmarshal(criRaw, cri) + if err != nil { + return nil, nil, errors.Wrap(err, "failed unmarshalling credential revocation information") + } + + disclosure := make([]byte, len(attributes)) + for i := 0; i < len(attributes); i++ { + if attributes[i].Type == bccsp.IdemixHiddenAttribute { + disclosure[i] = 0 + } else { + disclosure[i] = 1 + } + } + + sig, meta, err := s.Idemix.NewSignature( + credential, + sk, + Nym, + RNym, + iipk.PK, + disclosure, + msg, + rhIndex, + eidIndex, + cri, + newRandOrPanic(s.Idemix.Curve), + s.Translator, + sigType, + metadata, + ) + if err != nil { + return nil, nil, errors.WithMessage(err, "failed creating new signature") + } + + sigBytes, err := proto.Marshal(sig) + if err != nil { + return nil, nil, errors.WithMessage(err, "marshalling error") + } + + return sigBytes, meta, nil +} + +// AuditNymEid Audits the pseudonymous enrollment id of a signature +func (s *SignatureScheme) AuditNymEid( + ipk types.IssuerPublicKey, + eidIndex, _ int, + signature []byte, + enrollmentID string, + RNymEid *math.Zr, + verType bccsp.AuditVerificationType, +) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + eidAttr := s.Idemix.Curve.HashToZr([]byte(enrollmentID)) + + switch verType { + case bccsp.AuditExpectSignature: + sig := &idemix.Signature{} + err = proto.Unmarshal(signature, sig) + if err != nil { + return err + } + return sig.AuditNymEid( + iipk.PK, + eidAttr, + eidIndex, + RNymEid, + s.Idemix.Curve, + s.Translator, + ) + case bccsp.AuditExpectEidNymRhNym: + fallthrough + case bccsp.AuditExpectEidNym: + // 1. cast signature to NymEID + nymEID := idemix.NymEID(signature) + // 2. check audit on nymEID + return nymEID.AuditNymEid( + iipk.PK, + eidAttr, + eidIndex, + RNymEid, + s.Idemix.Curve, + s.Translator, + ) + default: + return errors.Errorf("invalid audit type [%d]", verType) + } +} + +// AuditNymRh Audits the pseudonymous revocation handle of a signature +func (s *SignatureScheme) AuditNymRh( + ipk types.IssuerPublicKey, + rhIndex, _ int, + signature []byte, + revocationHandle string, + RNymRh *math.Zr, + verType bccsp.AuditVerificationType, +) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + rhAttr := s.Idemix.Curve.HashToZr([]byte(revocationHandle)) + + switch verType { + case bccsp.AuditExpectSignature: + sig := &idemix.Signature{} + err = proto.Unmarshal(signature, sig) + if err != nil { + return err + } + return sig.AuditNymRh( + iipk.PK, + rhAttr, + rhIndex, + RNymRh, + s.Idemix.Curve, + s.Translator, + ) + case bccsp.AuditExpectEidNymRhNym: + // 1. cast signature to NymRH + nymRH := idemix.NymRH(signature) + // 2. check audit on nymRH + return nymRH.AuditNymRh( + iipk.PK, + rhAttr, + rhIndex, + RNymRh, + s.Idemix.Curve, + s.Translator, + ) + default: + return errors.Errorf("invalid audit type [%d]", verType) + } +} + +// Verify checks that an idemix signature is valid with the respect to the passed issuer public key, digest, attributes, +// revocation index (rhIndex), revocation public key, and epoch. +func (s *SignatureScheme) Verify( + ipk types.IssuerPublicKey, + signature, digest []byte, + attributes []bccsp.IdemixAttribute, + rhIndex, eidIndex, _ int, + revocationPublicKey *ecdsa.PublicKey, + epoch int, + verType bccsp.VerificationType, + meta *bccsp.IdemixSignerMetadata, +) (err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + sig := &idemix.Signature{} + err = proto.Unmarshal(signature, sig) + if err != nil { + return err + } + disclosure := make([]byte, len(attributes)) + attrValues := make([]*math.Zr, len(attributes)) + for i := 0; i < len(attributes); i++ { + switch attributes[i].Type { + case bccsp.IdemixHiddenAttribute: + disclosure[i] = 0 + attrValues[i] = nil + case bccsp.IdemixBytesAttribute: + disclosure[i] = 1 + attrValues[i] = s.Idemix.Curve.HashToZr(attributes[i].Value.([]byte)) + case bccsp.IdemixIntAttribute: + var value int64 + if v, ok := attributes[i].Value.(int); ok { + value = int64(v) + } else if v, ok := attributes[i].Value.(int64); ok { + value = v + } else { + return errors.Errorf("invalid int type for IdemixIntAttribute attribute") + } + + disclosure[i] = 1 + attrValues[i] = s.Idemix.Curve.NewZrFromInt(value) + default: + err = errors.Errorf("attribute type not allowed or supported [%v] at position [%d]", attributes[i].Type, i) + } + } + if err != nil { + return + } + + return sig.Ver( + disclosure, + iipk.PK, + digest, + attrValues, + rhIndex, + eidIndex, + revocationPublicKey, + epoch, + s.Idemix.Curve, + s.Translator, + verType, + meta, + ) +} diff --git a/v2/bccsp/schemes/dlog/bridge/user.go b/v2/bccsp/schemes/dlog/bridge/user.go new file mode 100644 index 0000000..6d35005 --- /dev/null +++ b/v2/bccsp/schemes/dlog/bridge/user.go @@ -0,0 +1,96 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package bridge + +import ( + "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// User encapsulates the idemix algorithms to generate user secret keys and pseudonym. +type User struct { + Translator idemix.Translator + Idemix *idemix.Idemix +} + +// NewKey generates an idemix user secret key +func (u *User) NewKey() (res *math.Zr, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + res = u.Idemix.Curve.NewRandomZr(newRandOrPanic(u.Idemix.Curve)) + + return +} + +func (u *User) NewKeyFromBytes(raw []byte) (res *math.Zr, err error) { + if len(raw) != u.Idemix.Curve.ScalarByteSize { + return nil, errors.Errorf("invalid length, expected [%d], got [%d]", u.Idemix.Curve.ScalarByteSize, len(raw)) + } + + res = u.Idemix.Curve.NewZrFromBytes(raw) + + return +} + +// MakeNym generates a new pseudonym key-pair derived from the passed user secret key (sk) and issuer public key (ipk) +func (u *User) MakeNym(sk *math.Zr, ipk types.IssuerPublicKey) (r1 *math.G1, r2 *math.Zr, err error) { + defer func() { + if r := recover(); r != nil { + r1 = nil + r2 = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + iipk, ok := ipk.(*IssuerPublicKey) + if !ok { + return nil, nil, errors.Errorf("invalid issuer public key, expected *IssuerPublicKey, got [%T]", ipk) + } + + ecp, big, err := u.Idemix.MakeNym(sk, iipk.PK, newRandOrPanic(u.Idemix.Curve), u.Translator) + + r1 = ecp + r2 = big + + return +} + +// MakeNym generates a new pseudonym key-pair derived from the passed user secret key (sk) and issuer public key (ipk) +func (u *User) NewNymFromBytes(raw []byte) (r1 *math.G1, r2 *math.Zr, err error) { + defer func() { + if r := recover(); r != nil { + r1 = nil + r2 = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + ecp, big, err := u.Idemix.MakeNymFromBytes(raw) + r1 = ecp + r2 = big + + return +} + +func (u *User) NewPublicNymFromBytes(raw []byte) (res *math.G1, err error) { + defer func() { + if r := recover(); r != nil { + res = nil + err = errors.Errorf("failure [%s]", r) + } + }() + + res, err = u.Translator.G1FromRawBytes(raw) + + return +} diff --git a/v2/bccsp/schemes/dlog/crypto/credential.go b/v2/bccsp/schemes/dlog/crypto/credential.go new file mode 100644 index 0000000..3436fbf --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/credential.go @@ -0,0 +1,220 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + amcl "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +type Translator interface { + G1ToProto(*math.G1) *amcl.ECP + G1FromProto(*amcl.ECP) (*math.G1, error) + G1FromRawBytes([]byte) (*math.G1, error) + G2ToProto(*math.G2) *amcl.ECP2 + G2FromProto(*amcl.ECP2) (*math.G2, error) +} + +// Identity Mixer Credential is a list of attributes certified (signed) by the issuer +// A credential also contains a user secret key blindly signed by the issuer +// Without the secret key the credential cannot be used + +// Credential issuance is an interactive protocol between a user and an issuer +// The issuer takes its secret and public keys and user attribute values as input +// The user takes the issuer public key and user secret as input +// The issuance protocol consists of the following steps: +// 1) The issuer sends a random nonce to the user +// 2) The user creates a Credential Request using the public key of the issuer, user secret, and the nonce as input +// The request consists of a commitment to the user secret (can be seen as a public key) and a zero-knowledge proof +// of knowledge of the user secret key +// The user sends the credential request to the issuer +// 3) The issuer verifies the credential request by verifying the zero-knowledge proof +// If the request is valid, the issuer issues a credential to the user by signing the commitment to the secret key +// together with the attribute values and sends the credential back to the user +// 4) The user verifies the issuer's signature and stores the credential that consists of +// the signature value, a randomness used to create the signature, the user secret, and the attribute values + +// NewCredential issues a new credential, which is the last step of the interactive issuance protocol +// All attribute values are added by the issuer at this step and then signed together with a commitment to +// the user's secret key from a credential request +func (i *Idemix) NewCredential(key *IssuerKey, m *CredRequest, attrs []*math.Zr, rng io.Reader, t Translator) (*Credential, error) { + return newCredential(key, m, attrs, rng, t, i.Curve) +} + +func newCredential(key *IssuerKey, m *CredRequest, attrs []*math.Zr, rng io.Reader, t Translator, curve *math.Curve) (*Credential, error) { + // check the credential request that contains + err := m.Check(key.Ipk, curve, t) + if err != nil { + return nil, err + } + + if len(attrs) != len(key.Ipk.AttributeNames) { + return nil, errors.Errorf("incorrect number of attribute values passed") + } + + // Place a BBS+ signature on the user key and the attribute values + // (For BBS+, see e.g. "Constant-Size Dynamic k-TAA" by Man Ho Au, Willy Susilo, Yi Mu) + // or http://eprint.iacr.org/2016/663.pdf, Sec. 4.3. + + // For a credential, a BBS+ signature consists of the following three elements: + // 1. E, random value in the proper group + // 2. S, random value in the proper group + // 3. A as B^Exp where B=g_1 \cdot h_r^s \cdot h_sk^sk \cdot \prod_{i=1}^L h_i^{m_i} and Exp = \frac{1}{e+x} + // Notice that: + // h_r is h_0 in http://eprint.iacr.org/2016/663.pdf, Sec. 4.3. + + // Pick randomness E and S + E := curve.NewRandomZr(rng) + S := curve.NewRandomZr(rng) + + // Set B as g_1 \cdot h_r^s \cdot h_sk^sk \cdot \prod_{i=1}^L h_i^{m_i} and Exp = \frac{1}{e+x} + B := curve.NewG1() + B.Clone(curve.GenG1) // g_1 + Nym, err := t.G1FromProto(m.Nym) + if err != nil { + return nil, err + } + B.Add(Nym) // in this case, recall Nym=h_sk^sk + HRand, err := t.G1FromProto(key.Ipk.HRand) + if err != nil { + return nil, err + } + B.Add(HRand.Mul(S)) // h_r^s + + HAttrs := make([]*math.G1, len(key.Ipk.HAttrs)) + for i := range key.Ipk.HAttrs { + var err error + HAttrs[i], err = t.G1FromProto(key.Ipk.HAttrs[i]) + if err != nil { + return nil, err + } + } + + // Append attributes + // Use Mul2 instead of Mul as much as possible for efficiency reasones + for i := 0; i < len(attrs)/2; i++ { + B.Add( + // Add two attributes in one shot + HAttrs[2*i].Mul2( + attrs[2*i], + HAttrs[2*i+1], + attrs[2*i+1], + ), + ) + } + // Check for residue in case len(attrs)%2 is odd + if len(attrs)%2 != 0 { + B.Add(HAttrs[len(attrs)-1].Mul(attrs[len(attrs)-1])) + } + + // Set Exp as \frac{1}{e+x} + Exp := curve.ModAdd(curve.NewZrFromBytes(key.GetIsk()), E, curve.GroupOrder) + Exp.InvModP(curve.GroupOrder) + // Finalise A as B^Exp + A := B.Mul(Exp) + // The signature is now generated. + + // Notice that here we release also B, this does not harm security cause + // it can be compute publicly from the BBS+ signature itself. + CredAttrs := make([][]byte, len(attrs)) + for index, attribute := range attrs { + CredAttrs[index] = attribute.Bytes() + } + + return &Credential{ + A: t.G1ToProto(A), + B: t.G1ToProto(B), + E: E.Bytes(), + S: S.Bytes(), + Attrs: CredAttrs}, nil +} + +// Ver cryptographically verifies the credential by verifying the signature +// on the attribute values and user's secret key +func (cred *Credential) Ver(sk *math.Zr, ipk *IssuerPublicKey, curve *math.Curve, t Translator) error { + // Validate Input + + // - parse the credential + A, err := t.G1FromProto(cred.GetA()) + if err != nil { + return err + } + B, err := t.G1FromProto(cred.GetB()) + if err != nil { + return err + } + E := curve.NewZrFromBytes(cred.GetE()) + S := curve.NewZrFromBytes(cred.GetS()) + + // - verify that all attribute values are present + for i := 0; i < len(cred.GetAttrs()); i++ { + if cred.Attrs[i] == nil { + return errors.Errorf("credential has no value for attribute %s", ipk.AttributeNames[i]) + } + } + + HSk, err := t.G1FromProto(ipk.HSk) + if err != nil { + return err + } + + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return err + } + + HAttrs := make([]*math.G1, len(ipk.HAttrs)) + for i := range ipk.HAttrs { + var err error + HAttrs[i], err = t.G1FromProto(ipk.HAttrs[i]) + if err != nil { + return err + } + } + + // - verify cryptographic signature on the attributes and the user secret key + BPrime := curve.NewG1() + BPrime.Clone(curve.GenG1) + BPrime.Add(HSk.Mul2(sk, HRand, S)) + for i := 0; i < len(cred.Attrs)/2; i++ { + BPrime.Add( + HAttrs[2*i].Mul2( + curve.NewZrFromBytes(cred.Attrs[2*i]), + HAttrs[2*i+1], + curve.NewZrFromBytes(cred.Attrs[2*i+1]), + ), + ) + } + if len(cred.Attrs)%2 != 0 { + BPrime.Add(HAttrs[len(cred.Attrs)-1].Mul(curve.NewZrFromBytes(cred.Attrs[len(cred.Attrs)-1]))) + } + if !B.Equals(BPrime) { + return errors.Errorf("b-value from credential does not match the attribute values") + } + + W, err := t.G2FromProto(ipk.W) + if err != nil { + return err + } + + // Verify BBS+ signature. Namely: e(w \cdot g_2^e, A) =? e(g_2, B) + a := curve.GenG2.Mul(E) + a.Add(W) + a.Affine() + + left := curve.FExp(curve.Pairing(a, A)) + right := curve.FExp(curve.Pairing(curve.GenG2, B)) + + if !left.Equals(right) { + return errors.Errorf("credential is not cryptographically valid") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/credrequest.go b/v2/bccsp/schemes/dlog/crypto/credrequest.go new file mode 100644 index 0000000..7f5b8eb --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/credrequest.go @@ -0,0 +1,125 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// credRequestLabel is the label used in zero-knowledge proof (ZKP) to identify that this ZKP is a credential request +const credRequestLabel = "credRequest" + +// Credential issuance is an interactive protocol between a user and an issuer +// The issuer takes its secret and public keys and user attribute values as input +// The user takes the issuer public key and user secret as input +// The issuance protocol consists of the following steps: +// 1) The issuer sends a random nonce to the user +// 2) The user creates a Credential Request using the public key of the issuer, user secret, and the nonce as input +// The request consists of a commitment to the user secret (can be seen as a public key) and a zero-knowledge proof +// of knowledge of the user secret key +// The user sends the credential request to the issuer +// 3) The issuer verifies the credential request by verifying the zero-knowledge proof +// If the request is valid, the issuer issues a credential to the user by signing the commitment to the secret key +// together with the attribute values and sends the credential back to the user +// 4) The user verifies the issuer's signature and stores the credential that consists of +// the signature value, a randomness used to create the signature, the user secret, and the attribute values + +// NewCredRequest creates a new Credential Request, the first message of the interactive credential issuance protocol +// (from user to issuer) +func (i *Idemix) NewCredRequest(sk *math.Zr, IssuerNonce []byte, ipk *IssuerPublicKey, rng io.Reader, tr Translator) (*CredRequest, error) { + return newCredRequest(sk, IssuerNonce, ipk, rng, i.Curve, tr) +} + +func newCredRequest(sk *math.Zr, IssuerNonce []byte, ipk *IssuerPublicKey, rng io.Reader, curve *math.Curve, tr Translator) (*CredRequest, error) { + // Set Nym as h_{sk}^{sk} + HSk, err := tr.G1FromProto(ipk.HSk) + if err != nil { + return nil, err + } + Nym := HSk.Mul(sk) + + // generate a zero-knowledge proof of knowledge (ZK PoK) of the secret key + + // Sample the randomness needed for the proof + rSk := curve.NewRandomZr(rng) + + // Step 1: First message (t-values) + t := HSk.Mul(rSk) // t = h_{sk}^{r_{sk}}, cover Nym + + // Step 2: Compute the Fiat-Shamir hash, forming the challenge of the ZKP. + // proofData is the data being hashed, it consists of: + // the credential request label + // 3 elements of G1 each taking 2*math.FieldBytes+1 bytes + // hash of the issuer public key of length math.FieldBytes + // issuer nonce of length math.FieldBytes + proofData := make([]byte, len([]byte(credRequestLabel))+3*curve.G1ByteSize+2*curve.ScalarByteSize) + index := 0 + index = appendBytesString(proofData, index, credRequestLabel) + index = appendBytesG1(proofData, index, t) + index = appendBytesG1(proofData, index, HSk) + index = appendBytesG1(proofData, index, Nym) + index = appendBytes(proofData, index, IssuerNonce) + copy(proofData[index:], ipk.Hash) + proofC := curve.HashToZr(proofData) + + // Step 3: reply to the challenge message (s-values) + proofS := curve.ModAdd(curve.ModMul(proofC, sk, curve.GroupOrder), rSk, curve.GroupOrder) // s = r_{sk} + C \cdot sk + + // Done + return &CredRequest{ + Nym: tr.G1ToProto(Nym), + IssuerNonce: IssuerNonce, + ProofC: proofC.Bytes(), + ProofS: proofS.Bytes(), + }, nil +} + +// Check cryptographically verifies the credential request +func (m *CredRequest) Check(ipk *IssuerPublicKey, curve *math.Curve, tr Translator) error { + Nym, err := tr.G1FromProto(m.GetNym()) + if err != nil { + return err + } + + IssuerNonce := m.GetIssuerNonce() + ProofC := curve.NewZrFromBytes(m.GetProofC()) + ProofS := curve.NewZrFromBytes(m.GetProofS()) + + HSk, err := tr.G1FromProto(ipk.HSk) + if err != nil { + return err + } + + if Nym == nil || IssuerNonce == nil || ProofC == nil || ProofS == nil { + return errors.Errorf("one of the proof values is undefined") + } + + // Verify Proof + + // Recompute t-values using s-values + t := HSk.Mul(ProofS) + t.Sub(Nym.Mul(ProofC)) // t = h_{sk}^s / Nym^C + + // Recompute challenge + proofData := make([]byte, len([]byte(credRequestLabel))+3*curve.G1ByteSize+2*curve.ScalarByteSize) + index := 0 + index = appendBytesString(proofData, index, credRequestLabel) + index = appendBytesG1(proofData, index, t) + index = appendBytesG1(proofData, index, HSk) + index = appendBytesG1(proofData, index, Nym) + index = appendBytes(proofData, index, IssuerNonce) + copy(proofData[index:], ipk.Hash) + + if !ProofC.Equals(curve.HashToZr(proofData)) { + return errors.Errorf("zero knowledge proof is invalid") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/idemix.go b/v2/bccsp/schemes/dlog/crypto/idemix.go new file mode 100644 index 0000000..72ece27 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/idemix.go @@ -0,0 +1,16 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + math "github.com/IBM/mathlib" +) + +type Idemix struct { + Curve *math.Curve + Translator Translator +} diff --git a/v2/bccsp/schemes/dlog/crypto/idemix.pb.go b/v2/bccsp/schemes/dlog/crypto/idemix.pb.go new file mode 100644 index 0000000..747cc31 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/idemix.pb.go @@ -0,0 +1,897 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: bccsp/schemes/dlog/crypto/idemix.proto + +package idemix + +import ( + fmt "fmt" + math "math" + + amcl "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// IssuerPublicKey specifies an issuer public key that consists of +// attribute_names - a list of the attribute names of a credential issued by the issuer +// h_sk, h_rand, h_attrs, w, bar_g1, bar_g2 - group elements corresponding to the signing key, randomness, and attributes +// proof_c, proof_s compose a zero-knowledge proof of knowledge of the secret key +// hash is a hash of the public key appended to it +type IssuerPublicKey struct { + AttributeNames []string `protobuf:"bytes,1,rep,name=attribute_names,json=attributeNames,proto3" json:"attribute_names,omitempty"` + HSk *amcl.ECP `protobuf:"bytes,2,opt,name=h_sk,json=hSk,proto3" json:"h_sk,omitempty"` + HRand *amcl.ECP `protobuf:"bytes,3,opt,name=h_rand,json=hRand,proto3" json:"h_rand,omitempty"` + HAttrs []*amcl.ECP `protobuf:"bytes,4,rep,name=h_attrs,json=hAttrs,proto3" json:"h_attrs,omitempty"` + W *amcl.ECP2 `protobuf:"bytes,5,opt,name=w,proto3" json:"w,omitempty"` + BarG1 *amcl.ECP `protobuf:"bytes,6,opt,name=bar_g1,json=barG1,proto3" json:"bar_g1,omitempty"` + BarG2 *amcl.ECP `protobuf:"bytes,7,opt,name=bar_g2,json=barG2,proto3" json:"bar_g2,omitempty"` + ProofC []byte `protobuf:"bytes,8,opt,name=proof_c,json=proofC,proto3" json:"proof_c,omitempty"` + ProofS []byte `protobuf:"bytes,9,opt,name=proof_s,json=proofS,proto3" json:"proof_s,omitempty"` + Hash []byte `protobuf:"bytes,10,opt,name=hash,proto3" json:"hash,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IssuerPublicKey) Reset() { *m = IssuerPublicKey{} } +func (m *IssuerPublicKey) String() string { return proto.CompactTextString(m) } +func (*IssuerPublicKey) ProtoMessage() {} +func (*IssuerPublicKey) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{0} +} + +func (m *IssuerPublicKey) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IssuerPublicKey.Unmarshal(m, b) +} +func (m *IssuerPublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IssuerPublicKey.Marshal(b, m, deterministic) +} +func (m *IssuerPublicKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_IssuerPublicKey.Merge(m, src) +} +func (m *IssuerPublicKey) XXX_Size() int { + return xxx_messageInfo_IssuerPublicKey.Size(m) +} +func (m *IssuerPublicKey) XXX_DiscardUnknown() { + xxx_messageInfo_IssuerPublicKey.DiscardUnknown(m) +} + +var xxx_messageInfo_IssuerPublicKey proto.InternalMessageInfo + +func (m *IssuerPublicKey) GetAttributeNames() []string { + if m != nil { + return m.AttributeNames + } + return nil +} + +func (m *IssuerPublicKey) GetHSk() *amcl.ECP { + if m != nil { + return m.HSk + } + return nil +} + +func (m *IssuerPublicKey) GetHRand() *amcl.ECP { + if m != nil { + return m.HRand + } + return nil +} + +func (m *IssuerPublicKey) GetHAttrs() []*amcl.ECP { + if m != nil { + return m.HAttrs + } + return nil +} + +func (m *IssuerPublicKey) GetW() *amcl.ECP2 { + if m != nil { + return m.W + } + return nil +} + +func (m *IssuerPublicKey) GetBarG1() *amcl.ECP { + if m != nil { + return m.BarG1 + } + return nil +} + +func (m *IssuerPublicKey) GetBarG2() *amcl.ECP { + if m != nil { + return m.BarG2 + } + return nil +} + +func (m *IssuerPublicKey) GetProofC() []byte { + if m != nil { + return m.ProofC + } + return nil +} + +func (m *IssuerPublicKey) GetProofS() []byte { + if m != nil { + return m.ProofS + } + return nil +} + +func (m *IssuerPublicKey) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +// IssuerKey specifies an issuer key pair that consists of +// ISk - the issuer secret key and +// IssuerPublicKey - the issuer public key +type IssuerKey struct { + Isk []byte `protobuf:"bytes,1,opt,name=isk,proto3" json:"isk,omitempty"` + Ipk *IssuerPublicKey `protobuf:"bytes,2,opt,name=ipk,proto3" json:"ipk,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IssuerKey) Reset() { *m = IssuerKey{} } +func (m *IssuerKey) String() string { return proto.CompactTextString(m) } +func (*IssuerKey) ProtoMessage() {} +func (*IssuerKey) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{1} +} + +func (m *IssuerKey) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IssuerKey.Unmarshal(m, b) +} +func (m *IssuerKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IssuerKey.Marshal(b, m, deterministic) +} +func (m *IssuerKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_IssuerKey.Merge(m, src) +} +func (m *IssuerKey) XXX_Size() int { + return xxx_messageInfo_IssuerKey.Size(m) +} +func (m *IssuerKey) XXX_DiscardUnknown() { + xxx_messageInfo_IssuerKey.DiscardUnknown(m) +} + +var xxx_messageInfo_IssuerKey proto.InternalMessageInfo + +func (m *IssuerKey) GetIsk() []byte { + if m != nil { + return m.Isk + } + return nil +} + +func (m *IssuerKey) GetIpk() *IssuerPublicKey { + if m != nil { + return m.Ipk + } + return nil +} + +// Credential specifies a credential object that consists of +// a, b, e, s - signature value +// attrs - attribute values +type Credential struct { + A *amcl.ECP `protobuf:"bytes,1,opt,name=a,proto3" json:"a,omitempty"` + B *amcl.ECP `protobuf:"bytes,2,opt,name=b,proto3" json:"b,omitempty"` + E []byte `protobuf:"bytes,3,opt,name=e,proto3" json:"e,omitempty"` + S []byte `protobuf:"bytes,4,opt,name=s,proto3" json:"s,omitempty"` + Attrs [][]byte `protobuf:"bytes,5,rep,name=attrs,proto3" json:"attrs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Credential) Reset() { *m = Credential{} } +func (m *Credential) String() string { return proto.CompactTextString(m) } +func (*Credential) ProtoMessage() {} +func (*Credential) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{2} +} + +func (m *Credential) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Credential.Unmarshal(m, b) +} +func (m *Credential) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Credential.Marshal(b, m, deterministic) +} +func (m *Credential) XXX_Merge(src proto.Message) { + xxx_messageInfo_Credential.Merge(m, src) +} +func (m *Credential) XXX_Size() int { + return xxx_messageInfo_Credential.Size(m) +} +func (m *Credential) XXX_DiscardUnknown() { + xxx_messageInfo_Credential.DiscardUnknown(m) +} + +var xxx_messageInfo_Credential proto.InternalMessageInfo + +func (m *Credential) GetA() *amcl.ECP { + if m != nil { + return m.A + } + return nil +} + +func (m *Credential) GetB() *amcl.ECP { + if m != nil { + return m.B + } + return nil +} + +func (m *Credential) GetE() []byte { + if m != nil { + return m.E + } + return nil +} + +func (m *Credential) GetS() []byte { + if m != nil { + return m.S + } + return nil +} + +func (m *Credential) GetAttrs() [][]byte { + if m != nil { + return m.Attrs + } + return nil +} + +// CredRequest specifies a credential request object that consists of +// nym - a pseudonym, which is a commitment to the user secret +// issuer_nonce - a random nonce provided by the issuer +// proof_c, proof_s - a zero-knowledge proof of knowledge of the +// user secret inside Nym +type CredRequest struct { + Nym *amcl.ECP `protobuf:"bytes,1,opt,name=nym,proto3" json:"nym,omitempty"` + IssuerNonce []byte `protobuf:"bytes,2,opt,name=issuer_nonce,json=issuerNonce,proto3" json:"issuer_nonce,omitempty"` + ProofC []byte `protobuf:"bytes,3,opt,name=proof_c,json=proofC,proto3" json:"proof_c,omitempty"` + ProofS []byte `protobuf:"bytes,4,opt,name=proof_s,json=proofS,proto3" json:"proof_s,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CredRequest) Reset() { *m = CredRequest{} } +func (m *CredRequest) String() string { return proto.CompactTextString(m) } +func (*CredRequest) ProtoMessage() {} +func (*CredRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{3} +} + +func (m *CredRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CredRequest.Unmarshal(m, b) +} +func (m *CredRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CredRequest.Marshal(b, m, deterministic) +} +func (m *CredRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CredRequest.Merge(m, src) +} +func (m *CredRequest) XXX_Size() int { + return xxx_messageInfo_CredRequest.Size(m) +} +func (m *CredRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CredRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CredRequest proto.InternalMessageInfo + +func (m *CredRequest) GetNym() *amcl.ECP { + if m != nil { + return m.Nym + } + return nil +} + +func (m *CredRequest) GetIssuerNonce() []byte { + if m != nil { + return m.IssuerNonce + } + return nil +} + +func (m *CredRequest) GetProofC() []byte { + if m != nil { + return m.ProofC + } + return nil +} + +func (m *CredRequest) GetProofS() []byte { + if m != nil { + return m.ProofS + } + return nil +} + +// EIDNym specifies a pseudonymous enrollment id object that consists of +// nym - pseudonymous enrollment id +// s_eid - field element +type EIDNym struct { + Nym *amcl.ECP `protobuf:"bytes,1,opt,name=nym,proto3" json:"nym,omitempty"` + ProofSEid []byte `protobuf:"bytes,2,opt,name=proof_s_eid,json=proofSEid,proto3" json:"proof_s_eid,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EIDNym) Reset() { *m = EIDNym{} } +func (m *EIDNym) String() string { return proto.CompactTextString(m) } +func (*EIDNym) ProtoMessage() {} +func (*EIDNym) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{4} +} + +func (m *EIDNym) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EIDNym.Unmarshal(m, b) +} +func (m *EIDNym) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EIDNym.Marshal(b, m, deterministic) +} +func (m *EIDNym) XXX_Merge(src proto.Message) { + xxx_messageInfo_EIDNym.Merge(m, src) +} +func (m *EIDNym) XXX_Size() int { + return xxx_messageInfo_EIDNym.Size(m) +} +func (m *EIDNym) XXX_DiscardUnknown() { + xxx_messageInfo_EIDNym.DiscardUnknown(m) +} + +var xxx_messageInfo_EIDNym proto.InternalMessageInfo + +func (m *EIDNym) GetNym() *amcl.ECP { + if m != nil { + return m.Nym + } + return nil +} + +func (m *EIDNym) GetProofSEid() []byte { + if m != nil { + return m.ProofSEid + } + return nil +} + +// RHNym specifies a pseudonymous revocation handle object that consists of +// nym - pseudonymous revocation handle +// s_rh - field element +type RHNym struct { + Nym *amcl.ECP `protobuf:"bytes,1,opt,name=nym,proto3" json:"nym,omitempty"` + ProofSRh []byte `protobuf:"bytes,2,opt,name=proof_s_rh,json=proofSRh,proto3" json:"proof_s_rh,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RHNym) Reset() { *m = RHNym{} } +func (m *RHNym) String() string { return proto.CompactTextString(m) } +func (*RHNym) ProtoMessage() {} +func (*RHNym) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{5} +} + +func (m *RHNym) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RHNym.Unmarshal(m, b) +} +func (m *RHNym) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RHNym.Marshal(b, m, deterministic) +} +func (m *RHNym) XXX_Merge(src proto.Message) { + xxx_messageInfo_RHNym.Merge(m, src) +} +func (m *RHNym) XXX_Size() int { + return xxx_messageInfo_RHNym.Size(m) +} +func (m *RHNym) XXX_DiscardUnknown() { + xxx_messageInfo_RHNym.DiscardUnknown(m) +} + +var xxx_messageInfo_RHNym proto.InternalMessageInfo + +func (m *RHNym) GetNym() *amcl.ECP { + if m != nil { + return m.Nym + } + return nil +} + +func (m *RHNym) GetProofSRh() []byte { + if m != nil { + return m.ProofSRh + } + return nil +} + +// Signature specifies a signature object that consists of +// a_prime, a_bar, b_prime, proof_* - randomized credential signature values +// and a zero-knowledge proof of knowledge of a credential +// and the corresponding user secret together with the attribute values +// nonce - a fresh nonce used for the signature +// nym - a fresh pseudonym (a commitment to to the user secret) +type Signature struct { + APrime *amcl.ECP `protobuf:"bytes,1,opt,name=a_prime,json=aPrime,proto3" json:"a_prime,omitempty"` + ABar *amcl.ECP `protobuf:"bytes,2,opt,name=a_bar,json=aBar,proto3" json:"a_bar,omitempty"` + BPrime *amcl.ECP `protobuf:"bytes,3,opt,name=b_prime,json=bPrime,proto3" json:"b_prime,omitempty"` + ProofC []byte `protobuf:"bytes,4,opt,name=proof_c,json=proofC,proto3" json:"proof_c,omitempty"` + ProofSSk []byte `protobuf:"bytes,5,opt,name=proof_s_sk,json=proofSSk,proto3" json:"proof_s_sk,omitempty"` + ProofSE []byte `protobuf:"bytes,6,opt,name=proof_s_e,json=proofSE,proto3" json:"proof_s_e,omitempty"` + ProofSR2 []byte `protobuf:"bytes,7,opt,name=proof_s_r2,json=proofSR2,proto3" json:"proof_s_r2,omitempty"` + ProofSR3 []byte `protobuf:"bytes,8,opt,name=proof_s_r3,json=proofSR3,proto3" json:"proof_s_r3,omitempty"` + ProofSSPrime []byte `protobuf:"bytes,9,opt,name=proof_s_s_prime,json=proofSSPrime,proto3" json:"proof_s_s_prime,omitempty"` + ProofSAttrs [][]byte `protobuf:"bytes,10,rep,name=proof_s_attrs,json=proofSAttrs,proto3" json:"proof_s_attrs,omitempty"` + Nonce []byte `protobuf:"bytes,11,opt,name=nonce,proto3" json:"nonce,omitempty"` + Nym *amcl.ECP `protobuf:"bytes,12,opt,name=nym,proto3" json:"nym,omitempty"` + ProofSRNym []byte `protobuf:"bytes,13,opt,name=proof_s_r_nym,json=proofSRNym,proto3" json:"proof_s_r_nym,omitempty"` + RevocationEpochPk *amcl.ECP2 `protobuf:"bytes,14,opt,name=revocation_epoch_pk,json=revocationEpochPk,proto3" json:"revocation_epoch_pk,omitempty"` + RevocationPkSig []byte `protobuf:"bytes,15,opt,name=revocation_pk_sig,json=revocationPkSig,proto3" json:"revocation_pk_sig,omitempty"` + Epoch int64 `protobuf:"varint,16,opt,name=epoch,proto3" json:"epoch,omitempty"` + NonRevocationProof *NonRevocationProof `protobuf:"bytes,17,opt,name=non_revocation_proof,json=nonRevocationProof,proto3" json:"non_revocation_proof,omitempty"` + EidNym *EIDNym `protobuf:"bytes,18,opt,name=eid_nym,json=eidNym,proto3" json:"eid_nym,omitempty"` + RhNym *RHNym `protobuf:"bytes,19,opt,name=rh_nym,json=rhNym,proto3" json:"rh_nym,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Signature) Reset() { *m = Signature{} } +func (m *Signature) String() string { return proto.CompactTextString(m) } +func (*Signature) ProtoMessage() {} +func (*Signature) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{6} +} + +func (m *Signature) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Signature.Unmarshal(m, b) +} +func (m *Signature) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Signature.Marshal(b, m, deterministic) +} +func (m *Signature) XXX_Merge(src proto.Message) { + xxx_messageInfo_Signature.Merge(m, src) +} +func (m *Signature) XXX_Size() int { + return xxx_messageInfo_Signature.Size(m) +} +func (m *Signature) XXX_DiscardUnknown() { + xxx_messageInfo_Signature.DiscardUnknown(m) +} + +var xxx_messageInfo_Signature proto.InternalMessageInfo + +func (m *Signature) GetAPrime() *amcl.ECP { + if m != nil { + return m.APrime + } + return nil +} + +func (m *Signature) GetABar() *amcl.ECP { + if m != nil { + return m.ABar + } + return nil +} + +func (m *Signature) GetBPrime() *amcl.ECP { + if m != nil { + return m.BPrime + } + return nil +} + +func (m *Signature) GetProofC() []byte { + if m != nil { + return m.ProofC + } + return nil +} + +func (m *Signature) GetProofSSk() []byte { + if m != nil { + return m.ProofSSk + } + return nil +} + +func (m *Signature) GetProofSE() []byte { + if m != nil { + return m.ProofSE + } + return nil +} + +func (m *Signature) GetProofSR2() []byte { + if m != nil { + return m.ProofSR2 + } + return nil +} + +func (m *Signature) GetProofSR3() []byte { + if m != nil { + return m.ProofSR3 + } + return nil +} + +func (m *Signature) GetProofSSPrime() []byte { + if m != nil { + return m.ProofSSPrime + } + return nil +} + +func (m *Signature) GetProofSAttrs() [][]byte { + if m != nil { + return m.ProofSAttrs + } + return nil +} + +func (m *Signature) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + +func (m *Signature) GetNym() *amcl.ECP { + if m != nil { + return m.Nym + } + return nil +} + +func (m *Signature) GetProofSRNym() []byte { + if m != nil { + return m.ProofSRNym + } + return nil +} + +func (m *Signature) GetRevocationEpochPk() *amcl.ECP2 { + if m != nil { + return m.RevocationEpochPk + } + return nil +} + +func (m *Signature) GetRevocationPkSig() []byte { + if m != nil { + return m.RevocationPkSig + } + return nil +} + +func (m *Signature) GetEpoch() int64 { + if m != nil { + return m.Epoch + } + return 0 +} + +func (m *Signature) GetNonRevocationProof() *NonRevocationProof { + if m != nil { + return m.NonRevocationProof + } + return nil +} + +func (m *Signature) GetEidNym() *EIDNym { + if m != nil { + return m.EidNym + } + return nil +} + +func (m *Signature) GetRhNym() *RHNym { + if m != nil { + return m.RhNym + } + return nil +} + +// NonRevocationProof contains proof that the credential is not revoked +type NonRevocationProof struct { + RevocationAlg int32 `protobuf:"varint,1,opt,name=revocation_alg,json=revocationAlg,proto3" json:"revocation_alg,omitempty"` + NonRevocationProof []byte `protobuf:"bytes,2,opt,name=non_revocation_proof,json=nonRevocationProof,proto3" json:"non_revocation_proof,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NonRevocationProof) Reset() { *m = NonRevocationProof{} } +func (m *NonRevocationProof) String() string { return proto.CompactTextString(m) } +func (*NonRevocationProof) ProtoMessage() {} +func (*NonRevocationProof) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{7} +} + +func (m *NonRevocationProof) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NonRevocationProof.Unmarshal(m, b) +} +func (m *NonRevocationProof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NonRevocationProof.Marshal(b, m, deterministic) +} +func (m *NonRevocationProof) XXX_Merge(src proto.Message) { + xxx_messageInfo_NonRevocationProof.Merge(m, src) +} +func (m *NonRevocationProof) XXX_Size() int { + return xxx_messageInfo_NonRevocationProof.Size(m) +} +func (m *NonRevocationProof) XXX_DiscardUnknown() { + xxx_messageInfo_NonRevocationProof.DiscardUnknown(m) +} + +var xxx_messageInfo_NonRevocationProof proto.InternalMessageInfo + +func (m *NonRevocationProof) GetRevocationAlg() int32 { + if m != nil { + return m.RevocationAlg + } + return 0 +} + +func (m *NonRevocationProof) GetNonRevocationProof() []byte { + if m != nil { + return m.NonRevocationProof + } + return nil +} + +// NymSignature specifies a signature object that signs a message +// with respect to a pseudonym. It differs from the standard idemix.signature in the fact that +// the standard signature object also proves that the pseudonym is based on a secret certified by +// a CA (issuer), whereas NymSignature only proves that the the owner of the pseudonym +// signed the message +type NymSignature struct { + // proof_c is the Fiat-Shamir challenge of the ZKP + ProofC []byte `protobuf:"bytes,1,opt,name=proof_c,json=proofC,proto3" json:"proof_c,omitempty"` + // proof_s_sk is the s-value proving knowledge of the user secret key + ProofSSk []byte `protobuf:"bytes,2,opt,name=proof_s_sk,json=proofSSk,proto3" json:"proof_s_sk,omitempty"` + // proof_s_r_nym is the s-value proving knowledge of the pseudonym secret + ProofSRNym []byte `protobuf:"bytes,3,opt,name=proof_s_r_nym,json=proofSRNym,proto3" json:"proof_s_r_nym,omitempty"` + // nonce is a fresh nonce used for the signature + Nonce []byte `protobuf:"bytes,4,opt,name=nonce,proto3" json:"nonce,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NymSignature) Reset() { *m = NymSignature{} } +func (m *NymSignature) String() string { return proto.CompactTextString(m) } +func (*NymSignature) ProtoMessage() {} +func (*NymSignature) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{8} +} + +func (m *NymSignature) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NymSignature.Unmarshal(m, b) +} +func (m *NymSignature) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NymSignature.Marshal(b, m, deterministic) +} +func (m *NymSignature) XXX_Merge(src proto.Message) { + xxx_messageInfo_NymSignature.Merge(m, src) +} +func (m *NymSignature) XXX_Size() int { + return xxx_messageInfo_NymSignature.Size(m) +} +func (m *NymSignature) XXX_DiscardUnknown() { + xxx_messageInfo_NymSignature.DiscardUnknown(m) +} + +var xxx_messageInfo_NymSignature proto.InternalMessageInfo + +func (m *NymSignature) GetProofC() []byte { + if m != nil { + return m.ProofC + } + return nil +} + +func (m *NymSignature) GetProofSSk() []byte { + if m != nil { + return m.ProofSSk + } + return nil +} + +func (m *NymSignature) GetProofSRNym() []byte { + if m != nil { + return m.ProofSRNym + } + return nil +} + +func (m *NymSignature) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + +type CredentialRevocationInformation struct { + // epoch contains the epoch (time window) in which this CRI is valid + Epoch int64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + // epoch_pk is the public key that is used by the revocation authority in this epoch + EpochPk *amcl.ECP2 `protobuf:"bytes,2,opt,name=epoch_pk,json=epochPk,proto3" json:"epoch_pk,omitempty"` + // epoch_pk_sig is a signature on the EpochPK valid under the revocation authority's long term key + EpochPkSig []byte `protobuf:"bytes,3,opt,name=epoch_pk_sig,json=epochPkSig,proto3" json:"epoch_pk_sig,omitempty"` + // revocation_alg denotes which revocation algorithm is used + RevocationAlg int32 `protobuf:"varint,4,opt,name=revocation_alg,json=revocationAlg,proto3" json:"revocation_alg,omitempty"` + // revocation_data contains data specific to the revocation algorithm used + RevocationData []byte `protobuf:"bytes,5,opt,name=revocation_data,json=revocationData,proto3" json:"revocation_data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CredentialRevocationInformation) Reset() { *m = CredentialRevocationInformation{} } +func (m *CredentialRevocationInformation) String() string { return proto.CompactTextString(m) } +func (*CredentialRevocationInformation) ProtoMessage() {} +func (*CredentialRevocationInformation) Descriptor() ([]byte, []int) { + return fileDescriptor_7f94c3d451691341, []int{9} +} + +func (m *CredentialRevocationInformation) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CredentialRevocationInformation.Unmarshal(m, b) +} +func (m *CredentialRevocationInformation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CredentialRevocationInformation.Marshal(b, m, deterministic) +} +func (m *CredentialRevocationInformation) XXX_Merge(src proto.Message) { + xxx_messageInfo_CredentialRevocationInformation.Merge(m, src) +} +func (m *CredentialRevocationInformation) XXX_Size() int { + return xxx_messageInfo_CredentialRevocationInformation.Size(m) +} +func (m *CredentialRevocationInformation) XXX_DiscardUnknown() { + xxx_messageInfo_CredentialRevocationInformation.DiscardUnknown(m) +} + +var xxx_messageInfo_CredentialRevocationInformation proto.InternalMessageInfo + +func (m *CredentialRevocationInformation) GetEpoch() int64 { + if m != nil { + return m.Epoch + } + return 0 +} + +func (m *CredentialRevocationInformation) GetEpochPk() *amcl.ECP2 { + if m != nil { + return m.EpochPk + } + return nil +} + +func (m *CredentialRevocationInformation) GetEpochPkSig() []byte { + if m != nil { + return m.EpochPkSig + } + return nil +} + +func (m *CredentialRevocationInformation) GetRevocationAlg() int32 { + if m != nil { + return m.RevocationAlg + } + return 0 +} + +func (m *CredentialRevocationInformation) GetRevocationData() []byte { + if m != nil { + return m.RevocationData + } + return nil +} + +func init() { + proto.RegisterType((*IssuerPublicKey)(nil), "idemix.IssuerPublicKey") + proto.RegisterType((*IssuerKey)(nil), "idemix.IssuerKey") + proto.RegisterType((*Credential)(nil), "idemix.Credential") + proto.RegisterType((*CredRequest)(nil), "idemix.CredRequest") + proto.RegisterType((*EIDNym)(nil), "idemix.EIDNym") + proto.RegisterType((*RHNym)(nil), "idemix.RHNym") + proto.RegisterType((*Signature)(nil), "idemix.Signature") + proto.RegisterType((*NonRevocationProof)(nil), "idemix.NonRevocationProof") + proto.RegisterType((*NymSignature)(nil), "idemix.NymSignature") + proto.RegisterType((*CredentialRevocationInformation)(nil), "idemix.CredentialRevocationInformation") +} + +func init() { + proto.RegisterFile("bccsp/schemes/dlog/crypto/idemix.proto", fileDescriptor_7f94c3d451691341) +} + +var fileDescriptor_7f94c3d451691341 = []byte{ + // 949 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x56, 0xcd, 0x8e, 0xe3, 0x44, + 0x10, 0x96, 0xe3, 0xd8, 0x99, 0x54, 0x3c, 0x33, 0xbb, 0xbd, 0x23, 0x8d, 0x35, 0xac, 0x96, 0x60, + 0x31, 0x4c, 0xe0, 0x30, 0x61, 0x33, 0x9c, 0xb8, 0x6d, 0x66, 0xa3, 0xdd, 0x08, 0x36, 0x8a, 0x3a, + 0x97, 0x15, 0x42, 0xb2, 0xda, 0x76, 0x6f, 0xdc, 0x4a, 0xfc, 0x43, 0xdb, 0x61, 0xc9, 0x05, 0x24, + 0x1e, 0x85, 0x23, 0x0f, 0xc1, 0x81, 0x23, 0x27, 0x9e, 0x81, 0x23, 0x4f, 0x81, 0xba, 0xdb, 0x7f, + 0x19, 0xcf, 0x2c, 0x97, 0xa4, 0xbb, 0xaa, 0xfa, 0xab, 0xea, 0xaf, 0xbe, 0xb2, 0x0d, 0x9f, 0x79, + 0xbe, 0x9f, 0xa5, 0xe3, 0xcc, 0x0f, 0x69, 0x44, 0xb3, 0x71, 0xb0, 0x4d, 0xd6, 0x63, 0x9f, 0xef, + 0xd3, 0x3c, 0x19, 0xb3, 0x80, 0x46, 0xec, 0xa7, 0xeb, 0x94, 0x27, 0x79, 0x82, 0x4c, 0xb5, 0xbb, + 0xf8, 0xea, 0xe1, 0xf8, 0x9c, 0x93, 0x38, 0xdb, 0x92, 0x3c, 0xe1, 0x63, 0x12, 0xf9, 0x5b, 0xf9, + 0xa3, 0x4e, 0x3b, 0x7f, 0x76, 0xe0, 0x74, 0x9e, 0x65, 0x3b, 0xca, 0x97, 0x3b, 0x6f, 0xcb, 0xfc, + 0x6f, 0xe8, 0x1e, 0x5d, 0xc1, 0x29, 0xc9, 0x73, 0xce, 0xbc, 0x5d, 0x4e, 0xdd, 0x98, 0x44, 0x34, + 0xb3, 0xb5, 0xa1, 0x3e, 0xea, 0xe3, 0x93, 0xca, 0xbc, 0x10, 0x56, 0xf4, 0x14, 0xba, 0xa1, 0x9b, + 0x6d, 0xec, 0xce, 0x50, 0x1b, 0x0d, 0x26, 0xfd, 0x6b, 0x89, 0x3b, 0xbb, 0x5d, 0x62, 0x3d, 0x5c, + 0x6d, 0xd0, 0x10, 0xcc, 0xd0, 0xe5, 0x24, 0x0e, 0x6c, 0xfd, 0xae, 0xdf, 0x08, 0x31, 0x89, 0x03, + 0xe4, 0x40, 0x2f, 0x74, 0x05, 0x66, 0x66, 0x77, 0x87, 0xfa, 0x61, 0x88, 0x19, 0xbe, 0x10, 0x0e, + 0x64, 0x83, 0xf6, 0xde, 0x36, 0x24, 0x00, 0x54, 0xde, 0x09, 0xd6, 0xde, 0x0b, 0x7c, 0x8f, 0x70, + 0x77, 0xfd, 0xdc, 0x36, 0x5b, 0xf8, 0x1e, 0xe1, 0xaf, 0x9e, 0x57, 0x11, 0x13, 0xbb, 0x77, 0x6f, + 0xc4, 0x04, 0x9d, 0x43, 0x2f, 0xe5, 0x49, 0xf2, 0xce, 0xf5, 0xed, 0xa3, 0xa1, 0x36, 0xb2, 0xb0, + 0x29, 0xb7, 0xb7, 0xb5, 0x23, 0xb3, 0xfb, 0x0d, 0xc7, 0x0a, 0x21, 0xe8, 0x86, 0x24, 0x0b, 0x6d, + 0x90, 0x56, 0xb9, 0x76, 0x5e, 0x43, 0x5f, 0x71, 0x28, 0xd8, 0x7b, 0x04, 0x3a, 0xcb, 0x36, 0xb6, + 0x26, 0xfd, 0x62, 0x89, 0x3e, 0x07, 0x9d, 0xa5, 0x25, 0x4b, 0xe7, 0xd7, 0x45, 0xf7, 0xee, 0xb0, + 0x8e, 0x45, 0x8c, 0x93, 0x02, 0xdc, 0x72, 0x1a, 0xd0, 0x38, 0x67, 0x64, 0x8b, 0xce, 0x41, 0x23, + 0x12, 0xe8, 0xa0, 0x74, 0x8d, 0x08, 0x87, 0xd7, 0x66, 0x5d, 0xf3, 0x90, 0x05, 0x1a, 0x95, 0x74, + 0x5b, 0x58, 0xa3, 0x62, 0x27, 0x98, 0x95, 0xbb, 0x0c, 0x9d, 0x81, 0xa1, 0xb8, 0x36, 0x86, 0xfa, + 0xc8, 0xc2, 0x6a, 0xe3, 0xfc, 0x02, 0x03, 0x91, 0x11, 0xd3, 0x1f, 0x76, 0x34, 0xcb, 0xd1, 0x47, + 0xa0, 0xc7, 0xfb, 0xa8, 0x9d, 0x54, 0x58, 0xd1, 0x27, 0x60, 0x31, 0x59, 0xb5, 0x1b, 0x27, 0xb1, + 0x4f, 0x65, 0x05, 0x16, 0x1e, 0x28, 0xdb, 0x42, 0x98, 0x9a, 0x84, 0xea, 0x0f, 0x11, 0xda, 0x6d, + 0x12, 0xea, 0xcc, 0xc0, 0x9c, 0xcd, 0x5f, 0x2e, 0xf6, 0xd1, 0x87, 0x73, 0x3f, 0x83, 0x41, 0x71, + 0xde, 0xa5, 0x2c, 0x28, 0x52, 0xf7, 0x15, 0xc6, 0x8c, 0x05, 0xce, 0x14, 0x0c, 0xfc, 0xfa, 0x7f, + 0x51, 0x9e, 0x02, 0x94, 0x28, 0x3c, 0x2c, 0x40, 0x8e, 0x14, 0x08, 0x0e, 0x9d, 0x3f, 0x0c, 0xe8, + 0xaf, 0xd8, 0x3a, 0x26, 0xf9, 0x8e, 0x53, 0xa1, 0x4e, 0xe2, 0xa6, 0x9c, 0x45, 0xb4, 0x0d, 0x66, + 0x92, 0xa5, 0x70, 0xa0, 0x67, 0x60, 0x10, 0xd7, 0x23, 0xbc, 0xdd, 0x8c, 0x2e, 0x99, 0x12, 0x2e, + 0x30, 0xbc, 0x02, 0xa3, 0x35, 0x04, 0xa6, 0xa7, 0x30, 0x1a, 0x94, 0x75, 0x0f, 0x28, 0x6b, 0x14, + 0x9b, 0x6d, 0xe4, 0x0c, 0x54, 0xc5, 0xae, 0x36, 0xe8, 0x02, 0xfa, 0x15, 0x21, 0x72, 0x02, 0x2c, + 0xdc, 0x2b, 0xe8, 0x38, 0xb8, 0xa6, 0x12, 0x7f, 0x7d, 0xcd, 0xc9, 0x81, 0xf7, 0xa6, 0xd0, 0x7d, + 0xe9, 0xbd, 0x41, 0x97, 0x70, 0x5a, 0x65, 0x2d, 0x4a, 0x57, 0x13, 0x60, 0x15, 0xa9, 0x55, 0xd5, + 0x0e, 0x1c, 0x97, 0x61, 0x4a, 0x55, 0x20, 0x55, 0xa5, 0x9a, 0xb4, 0x52, 0xb3, 0x7b, 0x06, 0x86, + 0x12, 0xca, 0x40, 0x02, 0xa8, 0x4d, 0xd9, 0x20, 0xeb, 0x01, 0x89, 0x55, 0xb0, 0xdc, 0x15, 0x61, + 0xc7, 0xf2, 0x28, 0x14, 0xe5, 0x89, 0x06, 0x7f, 0x0d, 0x4f, 0x38, 0xfd, 0x31, 0xf1, 0x49, 0xce, + 0x92, 0xd8, 0xa5, 0x69, 0xe2, 0x87, 0x6e, 0xba, 0xb1, 0x4f, 0x5a, 0xcf, 0x88, 0xc7, 0x75, 0xd8, + 0x4c, 0x44, 0x2d, 0x37, 0xe8, 0x0b, 0x68, 0x18, 0xdd, 0x74, 0xe3, 0x66, 0x6c, 0x6d, 0x9f, 0xca, + 0x14, 0xa7, 0xb5, 0x63, 0xb9, 0x59, 0xb1, 0xb5, 0xa8, 0x5e, 0x82, 0xdb, 0x8f, 0x86, 0xda, 0x48, + 0xc7, 0x6a, 0x83, 0xbe, 0x85, 0xb3, 0x38, 0x89, 0xdd, 0x26, 0x8a, 0x28, 0xcd, 0x7e, 0x2c, 0xd3, + 0x5f, 0x94, 0xd3, 0xbd, 0x48, 0x62, 0x5c, 0xe3, 0x89, 0x08, 0x8c, 0xe2, 0x96, 0x0d, 0x5d, 0x41, + 0x8f, 0xb2, 0x40, 0x5e, 0x14, 0x49, 0x80, 0x93, 0x12, 0x40, 0xcd, 0x04, 0x36, 0x29, 0x0b, 0xc4, + 0xa5, 0x3f, 0x05, 0x93, 0x87, 0x32, 0xee, 0x89, 0x8c, 0x3b, 0x2e, 0xe3, 0xa4, 0xe8, 0xb1, 0xc1, + 0xc3, 0xc5, 0x3e, 0x72, 0x22, 0x40, 0xed, 0xc4, 0xe8, 0x12, 0x4e, 0x1a, 0xe5, 0x92, 0xed, 0x5a, + 0xea, 0xd9, 0xc0, 0xc7, 0xb5, 0xf5, 0xc5, 0x76, 0x8d, 0xbe, 0x7c, 0xe0, 0x66, 0x6a, 0x4a, 0xee, + 0xa9, 0xde, 0xf9, 0x19, 0xac, 0xc5, 0x3e, 0xaa, 0x27, 0xa6, 0xa1, 0x64, 0xed, 0x03, 0x4a, 0xee, + 0xdc, 0x51, 0x72, 0xab, 0xe7, 0x7a, 0xab, 0xe7, 0x95, 0x92, 0xba, 0x0d, 0x25, 0x39, 0x7f, 0x6b, + 0xf0, 0x71, 0xfd, 0xb8, 0xac, 0xab, 0x9b, 0xc7, 0xef, 0x12, 0x1e, 0xc9, 0x65, 0xdd, 0x45, 0xad, + 0xd9, 0xc5, 0x4b, 0x38, 0xaa, 0x84, 0xd3, 0x69, 0x09, 0xa7, 0x47, 0x0b, 0xb9, 0x0c, 0xc1, 0x2a, + 0xc3, 0xa4, 0x52, 0x8a, 0xc2, 0x0a, 0xb7, 0x10, 0x49, 0x9b, 0xdb, 0xee, 0x7d, 0xdc, 0x5e, 0x41, + 0x43, 0x5e, 0x6e, 0x40, 0x72, 0x52, 0xcc, 0x73, 0xe3, 0xf4, 0x4b, 0x92, 0x93, 0xe9, 0xaf, 0x1a, + 0x80, 0x9f, 0x44, 0x45, 0x77, 0xa7, 0x83, 0xb9, 0xfc, 0x5f, 0x8a, 0x77, 0xf5, 0x52, 0xfb, 0x6e, + 0xbc, 0x66, 0x79, 0xb8, 0xf3, 0xae, 0xfd, 0x24, 0x1a, 0xcf, 0xa7, 0x6f, 0x8a, 0x0f, 0x81, 0xf1, + 0x3d, 0x6f, 0x7e, 0xe5, 0xf9, 0xad, 0xa3, 0xcf, 0xdf, 0xbe, 0xfd, 0xbd, 0x63, 0x2a, 0x98, 0xbf, + 0xca, 0xc5, 0x3f, 0x1d, 0xa4, 0x16, 0xdf, 0xbf, 0x5a, 0x4e, 0xdf, 0xd0, 0x9c, 0x88, 0x8a, 0xfe, + 0x2d, 0xbd, 0x9e, 0x29, 0xbf, 0x0d, 0x6e, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x36, 0xd7, + 0xe8, 0x83, 0x08, 0x00, 0x00, +} diff --git a/v2/bccsp/schemes/dlog/crypto/idemix.proto b/v2/bccsp/schemes/dlog/crypto/idemix.proto new file mode 100644 index 0000000..769d465 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/idemix.proto @@ -0,0 +1,150 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +syntax = "proto3"; + +option go_package = "github.com/IBM/idemix/v2/bccsp/schemes/dlog/idemix"; + +// The Identity Mixer protocols make use of pairings (bilinear maps) - +// functions that can be described as e: G1 x G2 -> GT that +// map group elements from the source groups (G1 and G2) to the target group +// Such groups can be represented by the points on an elliptic curve + +package idemix; + +import "bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto"; + +// IssuerPublicKey specifies an issuer public key that consists of +// attribute_names - a list of the attribute names of a credential issued by the issuer +// h_sk, h_rand, h_attrs, w, bar_g1, bar_g2 - group elements corresponding to the signing key, randomness, and attributes +// proof_c, proof_s compose a zero-knowledge proof of knowledge of the secret key +// hash is a hash of the public key appended to it +message IssuerPublicKey { + repeated string attribute_names = 1; + amcl.ECP h_sk = 2; + amcl.ECP h_rand = 3; + repeated amcl.ECP h_attrs = 4; + amcl.ECP2 w = 5; + amcl.ECP bar_g1 = 6; + amcl.ECP bar_g2 = 7; + bytes proof_c = 8; + bytes proof_s = 9; + bytes hash = 10; +} + +// IssuerKey specifies an issuer key pair that consists of +// ISk - the issuer secret key and +// IssuerPublicKey - the issuer public key +message IssuerKey { + bytes isk = 1; + IssuerPublicKey ipk = 2; +} + +// Credential specifies a credential object that consists of +// a, b, e, s - signature value +// attrs - attribute values +message Credential { + amcl.ECP a = 1; + amcl.ECP b = 2; + bytes e = 3; + bytes s = 4; + repeated bytes attrs = 5; +} + +// CredRequest specifies a credential request object that consists of +// nym - a pseudonym, which is a commitment to the user secret +// issuer_nonce - a random nonce provided by the issuer +// proof_c, proof_s - a zero-knowledge proof of knowledge of the +// user secret inside Nym +message CredRequest { + amcl.ECP nym = 1; + bytes issuer_nonce = 2; + bytes proof_c = 3; + bytes proof_s = 4; +} + +// EIDNym specifies a pseudonymous enrollment id object that consists of +// nym - pseudonymous enrollment id +// s_eid - field element +message EIDNym { + amcl.ECP nym = 1; + bytes proof_s_eid = 2; +} + +// RHNym specifies a pseudonymous revocation handle object that consists of +// nym - pseudonymous revocation handle +// s_rh - field element +message RHNym { + amcl.ECP nym = 1; + bytes proof_s_rh = 2; +} + +// Signature specifies a signature object that consists of +// a_prime, a_bar, b_prime, proof_* - randomized credential signature values +// and a zero-knowledge proof of knowledge of a credential +// and the corresponding user secret together with the attribute values +// nonce - a fresh nonce used for the signature +// nym - a fresh pseudonym (a commitment to to the user secret) +message Signature { + amcl.ECP a_prime = 1; + amcl.ECP a_bar = 2; + amcl.ECP b_prime = 3; + bytes proof_c = 4; + bytes proof_s_sk = 5; + bytes proof_s_e = 6; + bytes proof_s_r2 = 7; + bytes proof_s_r3 = 8; + bytes proof_s_s_prime = 9; + repeated bytes proof_s_attrs = 10; + bytes nonce = 11; + amcl.ECP nym = 12; + bytes proof_s_r_nym = 13; + amcl.ECP2 revocation_epoch_pk = 14; + bytes revocation_pk_sig = 15; + int64 epoch = 16; + NonRevocationProof non_revocation_proof = 17; + EIDNym eid_nym = 18; + RHNym rh_nym = 19; +} + +// NonRevocationProof contains proof that the credential is not revoked +message NonRevocationProof { + int32 revocation_alg = 1; + bytes non_revocation_proof = 2; +} + +// NymSignature specifies a signature object that signs a message +// with respect to a pseudonym. It differs from the standard idemix.signature in the fact that +// the standard signature object also proves that the pseudonym is based on a secret certified by +// a CA (issuer), whereas NymSignature only proves that the the owner of the pseudonym +// signed the message +message NymSignature { + // proof_c is the Fiat-Shamir challenge of the ZKP + bytes proof_c = 1; + // proof_s_sk is the s-value proving knowledge of the user secret key + bytes proof_s_sk = 2; + //proof_s_r_nym is the s-value proving knowledge of the pseudonym secret + bytes proof_s_r_nym = 3; + // nonce is a fresh nonce used for the signature + bytes nonce = 4; +} + +message CredentialRevocationInformation { + // epoch contains the epoch (time window) in which this CRI is valid + int64 epoch = 1; + + // epoch_pk is the public key that is used by the revocation authority in this epoch + amcl.ECP2 epoch_pk = 2; + + // epoch_pk_sig is a signature on the EpochPK valid under the revocation authority's long term key + bytes epoch_pk_sig = 3; + + // revocation_alg denotes which revocation algorithm is used + int32 revocation_alg = 4; + + // revocation_data contains data specific to the revocation algorithm used + bytes revocation_data = 5; +} diff --git a/v2/bccsp/schemes/dlog/crypto/idemix_test.go b/v2/bccsp/schemes/dlog/crypto/idemix_test.go new file mode 100644 index 0000000..bd6f0aa --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/idemix_test.go @@ -0,0 +1,856 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "bytes" + "sync" + "testing" + + math "github.com/IBM/mathlib" + "github.com/stretchr/testify/require" + + weakbb "github.com/IBM/idemix/bccsp/schemes/weak-bb" + opts "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" +) + +type testEnv struct { + curve *math.Curve + tr Translator +} + +var envs = []testEnv{ + { + curve: math.Curves[math.FP256BN_AMCL], + tr: &amcl.Fp256bn{ + C: math.Curves[math.FP256BN_AMCL], + }, + }, + { + curve: math.Curves[math.FP256BN_AMCL_MIRACL], + tr: &amcl.Fp256bnMiracl{ + C: math.Curves[math.FP256BN_AMCL_MIRACL], + }, + }, + { + curve: math.Curves[math.BN254], + tr: &amcl.Gurvy{ + C: math.Curves[math.BN254], + }, + }, + { + curve: math.Curves[math.BLS12_381], + tr: &amcl.Gurvy{ + C: math.Curves[math.BLS12_381], + }, + }, + { + curve: math.Curves[math.BLS12_381_GURVY], + tr: &amcl.Gurvy{ + C: math.Curves[math.BLS12_381_GURVY], + }, + }, + { + curve: math.Curves[math.BLS12_377_GURVY], + tr: &amcl.Gurvy{ + C: math.Curves[math.BLS12_377_GURVY], + }, + }, +} + +func TestIdemixAllCurves(t *testing.T) { + for _, e := range envs { + testIdemix(t, e.curve, e.tr) + } +} + +func testIdemix(t *testing.T, curve *math.Curve, tr Translator) { + idmx := &Idemix{ + Curve: curve, + } + // Test weak BB sigs: + // Test KeyGen + rng, err := curve.Rand() + require.NoError(t, err) + wbbsk, wbbpk := weakbb.WbbKeyGen(curve, rng) + + // Get random message + testmsg := curve.NewRandomZr(rng) + + // Test Signing + wbbsig := weakbb.WbbSign(curve, wbbsk, testmsg) + + // Test Verification + err = weakbb.WbbVerify(curve, wbbpk, wbbsig, testmsg) + require.NoError(t, err) + + // Test idemix functionality + AttributeNames := []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + attrs := make([]*math.Zr, len(AttributeNames)) + for i := range AttributeNames { + attrs[i] = curve.NewZrFromInt(int64(i)) + } + + // Test issuer key generation + if err != nil { + t.Fatalf("Error getting rng: \"%s\"", err) + return + } + // Create a new key pair + key, err := idmx.NewIssuerKey(AttributeNames, rng, tr) + if err != nil { + t.Fatalf("Issuer key generation should have succeeded but gave error \"%s\"", err) + return + } + + // Check that the key is valid + err = key.GetIpk().Check(curve, tr) + if err != nil { + t.Fatalf("Issuer public key should be valid") + return + } + + // Make sure Check() is invalid for a public key with invalid proof + proofC := key.Ipk.GetProofC() + key.Ipk.ProofC = curve.NewRandomZr(rng).Bytes() + require.Error(t, key.Ipk.Check(curve, tr), "public key with broken zero-knowledge proof should be invalid") + + // Make sure Check() is invalid for a public key with incorrect number of HAttrs + hAttrs := key.Ipk.GetHAttrs() + key.Ipk.HAttrs = key.Ipk.HAttrs[:0] + require.Error(t, key.Ipk.Check(curve, tr), "public key with incorrect number of HAttrs should be invalid") + key.Ipk.HAttrs = hAttrs + + // Restore IPk to be valid + key.Ipk.ProofC = proofC + h := key.Ipk.GetHash() + require.NoError(t, key.Ipk.Check(curve, tr), "restored public key should be valid") + require.Zero(t, bytes.Compare(h, key.Ipk.GetHash()), "IPK hash changed on ipk Check") + + // Create public with duplicate attribute names should fail + _, err = idmx.NewIssuerKey([]string{"Attr1", "Attr2", "Attr1"}, rng, tr) + require.Error(t, err, "issuer key generation should fail with duplicate attribute names") + + // Test issuance + sk := curve.NewRandomZr(rng) + ni := curve.NewRandomZr(rng) + m, err := idmx.NewCredRequest(sk, ni.Bytes(), key.Ipk, rng, tr) + require.NoError(t, err, "NewCredRequest failed: \"%s\"", err) + + cred, err := idmx.NewCredential(key, m, attrs, rng, tr) + require.NoError(t, err, "Failed to issue a credential: \"%s\"", err) + + require.NoError(t, cred.Ver(sk, key.Ipk, idmx.Curve, tr), "credential should be valid") + + // Issuing a credential with the incorrect amount of attributes should fail + _, err = idmx.NewCredential(key, m, []*math.Zr{}, rng, tr) + require.Error(t, err, "issuing a credential with the incorrect amount of attributes should fail") + + // Breaking the ZK proof of the CredRequest should make it invalid + proofC = m.GetProofC() + m.ProofC = curve.NewRandomZr(rng).Bytes() + require.Error(t, m.Check(key.Ipk, idmx.Curve, tr), "CredRequest with broken ZK proof should not be valid") + + // Creating a credential from a broken CredRequest should fail + _, err = idmx.NewCredential(key, m, attrs, rng, tr) + require.Error(t, err, "creating a credential from an invalid CredRequest should fail") + m.ProofC = proofC + + // A credential with nil attribute should be invalid + attrsBackup := cred.GetAttrs() + cred.Attrs = [][]byte{nil, nil, nil, nil, nil} + require.Error(t, cred.Ver(sk, key.Ipk, idmx.Curve, tr), "credential with nil attribute should be invalid") + cred.Attrs = attrsBackup + + // Generate a revocation key pair + revocationKey, err := idmx.GenerateLongTermRevocationKey() + require.NoError(t, err) + + // Create CRI that contains no revocation mechanism + epoch := 0 + cri, err := idmx.CreateCRI(revocationKey, []*math.Zr{}, epoch, ALG_NO_REVOCATION, rng, tr) + require.NoError(t, err) + err = idmx.VerifyEpochPK(&revocationKey.PublicKey, cri.EpochPk, cri.EpochPkSig, int(cri.Epoch), RevocationAlgorithm(cri.RevocationAlg)) + require.NoError(t, err) + + // make sure that epoch pk is not valid in future epoch + err = idmx.VerifyEpochPK(&revocationKey.PublicKey, cri.EpochPk, cri.EpochPkSig, int(cri.Epoch)+1, RevocationAlgorithm(cri.RevocationAlg)) + require.Error(t, err) + + // Test bad input + _, err = idmx.CreateCRI(nil, []*math.Zr{}, epoch, ALG_NO_REVOCATION, rng, tr) + require.Error(t, err) + _, err = idmx.CreateCRI(revocationKey, []*math.Zr{}, epoch, ALG_NO_REVOCATION, nil, tr) + require.Error(t, err) + + // Test signing no disclosure + Nym, RandNym, err := idmx.MakeNym(sk, key.Ipk, rng, tr) + require.NoError(t, err, "MakeNym failed: \"%s\"", err) + + disclosure := []byte{0, 0, 0, 0, 0} + msg := []byte{1, 2, 3, 4, 5} + rhindex := 4 + sig, _, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, 0, cri, rng, tr, opts.Standard, nil) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Fatalf("Signature should be valid but verification returned error: %s", err) + return + } + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + if err != nil { + t.Fatalf("Signature should be valid but verification returned error: %s", err) + return + } + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + require.Equal(t, "no EidNym provided but ExpectEidNym required", err.Error()) + + eidIndex := 2 + sig, meta, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, eidIndex, cri, rng, tr, opts.EidNym, nil) + require.NoError(t, err) + + // assert that the returned randomness is the right one + H_a_eid, err := tr.G1FromProto(key.Ipk.HAttrs[eidIndex]) + require.NoError(t, err, "G1FromProto failed: \"%s\"", err) + HRand, err := tr.G1FromProto(key.Ipk.HRand) + require.NoError(t, err, "G1FromProto failed: \"%s\"", err) + Nym_eid := H_a_eid.Mul2(attrs[eidIndex], HRand, meta.EidNymAuditData.Rand) + EidNym, err := tr.G1FromProto(sig.EidNym.Nym) + require.NoError(t, err, "G1FromProto failed: \"%s\"", err) + require.True(t, Nym_eid.Equals(EidNym)) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Fatalf("Signature should be valid but verification returned error: %s", err) + return + } + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.Error(t, err) + require.Equal(t, "EidNym available but ExpectStandard required", err.Error()) + + // supply the meta to audit the nym eid + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.NoError(t, err) + + sig, meta2, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, eidIndex, cri, rng, tr, opts.EidNym, meta) + require.NoError(t, err) + require.True(t, meta.EidNymAuditData.Rand.Equals(meta2.EidNymAuditData.Rand)) + require.True(t, meta.EidNymAuditData.Nym.Equals(meta2.EidNymAuditData.Nym)) + require.True(t, meta.EidNymAuditData.Attr.Equals(meta2.EidNymAuditData.Attr)) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.NoError(t, err) + meta2.EidNym = meta2.EidNymAuditData.Nym.Bytes() + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.NoError(t, err) + meta2.EidNym = []byte{0, 1, 2} + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.Equal(t, "signature invalid: nym eid validation failed, failed to unmarshal meta nym eid", err.Error()) + meta2.EidNym = meta2.EidNymAuditData.Nym.Mul(curve.NewZrFromInt(2)).Bytes() + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.Equal(t, "signature invalid: nym eid validation failed, signature nym eid does not match metadata", err.Error()) + meta2.EidNym = nil + meta2.EidNymAuditData.Nym = meta2.EidNymAuditData.Nym.Mul(curve.NewZrFromInt(2)) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match metadata", err.Error()) + meta2.EidNymAuditData.Nym = nil + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta2) + require.NoError(t, err) + + // tamper with the randomness of the nym eid to expect a failed verification + meta.EidNymAuditData.Attr = curve.NewZrFromInt(35) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + + // Test signing selective disclosure + disclosure = []byte{0, 1, 1, 1, 0} + sig, _, err = idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, 0, cri, rng, tr, opts.Standard, nil) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + require.Equal(t, "no EidNym provided but ExpectEidNym required", err.Error()) + + // Test NymSignatures + nymsig, err := idmx.NewNymSignature(sk, Nym, RandNym, key.Ipk, []byte("testing"), rng, tr) + require.NoError(t, err) + + err = nymsig.Ver(Nym, key.Ipk, []byte("testing"), idmx.Curve, tr) + if err != nil { + t.Fatalf("NymSig should be valid but verification returned error: %s", err) + return + } +} + +func TestCredentialVerParallelAMCL(t *testing.T) { + t.Parallel() + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + testCredentialVerParallel( + t, + curve, + &amcl.Fp256bnMiracl{C: curve}, + ) +} + +func TestCredentialVerParallelGurvy254(t *testing.T) { + t.Parallel() + curve := math.Curves[math.BN254] + testCredentialVerParallel( + t, + curve, + &amcl.Gurvy{C: curve}, + ) +} + +func testCredentialVerParallel(t *testing.T, curve *math.Curve, tr Translator) { + idmx := &Idemix{ + Curve: curve, + } + rng, err := curve.Rand() + require.NoError(t, err) + AttributeNames := []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + attrs := make([]*math.Zr, len(AttributeNames)) + for i := range AttributeNames { + attrs[i] = curve.NewZrFromInt(int64(i)) + } + // Create a new key pair + key, err := idmx.NewIssuerKey(AttributeNames, rng, tr) + require.NoError(t, err) + + // Check that the key is valid + err = key.GetIpk().Check(curve, tr) + require.NoError(t, err) + + // Test issuance + waitGroup := &sync.WaitGroup{} + waitGroup.Add(100) + for i := 0; i < 100; i++ { + go func() { + defer waitGroup.Done() + + rng, err := curve.Rand() + require.NoError(t, err) + sk := curve.NewRandomZr(rng) + ni := curve.NewRandomZr(rng) + m, err := idmx.NewCredRequest(sk, ni.Bytes(), key.Ipk, rng, tr) + require.NoError(t, err, "NewCredRequest failed: \"%s\"", err) + cred, err := idmx.NewCredential(key, m, attrs, rng, tr) + require.NoError(t, err, "Failed to issue a credential: \"%s\"", err) + require.NoError(t, cred.Ver(sk, key.Ipk, idmx.Curve, tr), "credential should be valid") + }() + } + waitGroup.Wait() +} + +func TestSigParallelAMCL(t *testing.T) { + t.Parallel() + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + testSigParallel( + t, + curve, + &amcl.Fp256bnMiracl{C: curve}, + ) +} + +func TestSigParallelGurvy254(t *testing.T) { + t.Parallel() + curve := math.Curves[math.BN254] + testSigParallel( + t, + curve, + &amcl.Gurvy{C: curve}, + ) +} + +func testSigParallel(t *testing.T, curve *math.Curve, tr Translator) { + idmx := &Idemix{ + Curve: curve, + } + // Test weak BB sigs: + // Test KeyGen + rng, err := curve.Rand() + require.NoError(t, err) + + // Test idemix functionality + AttributeNames := []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + attrs := make([]*math.Zr, len(AttributeNames)) + for i := range AttributeNames { + attrs[i] = curve.NewZrFromInt(int64(i)) + } + + // Create a new key pair + key, err := idmx.NewIssuerKey(AttributeNames, rng, tr) + require.NoError(t, err) + + // Check that the key is valid + err = key.GetIpk().Check(curve, tr) + require.NoError(t, err) + + // Test issuance + sk := curve.NewRandomZr(rng) + ni := curve.NewRandomZr(rng) + m, err := idmx.NewCredRequest(sk, ni.Bytes(), key.Ipk, rng, tr) + require.NoError(t, err, "NewCredRequest failed: \"%s\"", err) + + cred, err := idmx.NewCredential(key, m, attrs, rng, tr) + require.NoError(t, err, "Failed to issue a credential: \"%s\"", err) + require.NoError(t, cred.Ver(sk, key.Ipk, idmx.Curve, tr), "credential should be valid") + + // Generate a revocation key pair + revocationKey, err := idmx.GenerateLongTermRevocationKey() + require.NoError(t, err) + + // Create CRI that contains no revocation mechanism + epoch := 0 + cri, err := idmx.CreateCRI(revocationKey, []*math.Zr{}, epoch, ALG_NO_REVOCATION, rng, tr) + require.NoError(t, err) + err = idmx.VerifyEpochPK(&revocationKey.PublicKey, cri.EpochPk, cri.EpochPkSig, int(cri.Epoch), RevocationAlgorithm(cri.RevocationAlg)) + require.NoError(t, err) + + Nym, RandNym, err := idmx.MakeNym(sk, key.Ipk, rng, tr) + require.NoError(t, err, "MakeNym failed: \"%s\"", err) + + waitGroup := &sync.WaitGroup{} + n := 100 + + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + rng, _ := curve.Rand() + + // Test signing no disclosure + + disclosure := []byte{0, 0, 0, 0, 0} + msg := []byte{1, 2, 3, 4, 5} + rhindex := 4 + sig, _, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, 0, cri, rng, tr, opts.Standard, nil) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Logf("Signature should be valid but verification returned error: %s", err) + t.Fail() + return + } + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + if err != nil { + t.Logf("Signature should be valid but verification returned error: %s", err) + t.Fail() + return + } + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + require.Equal(t, "no EidNym provided but ExpectEidNym required", err.Error()) + + eidIndex := 2 + sig, meta, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, eidIndex, cri, rng, tr, opts.EidNym, nil) + require.NoError(t, err) + + // assert that the returned randomness is the right one + H_a_eid, err := tr.G1FromProto(key.Ipk.HAttrs[eidIndex]) + if err != nil { + t.Logf("G1FromProto returned error: %s", err) + t.Fail() + return + } + HRand, err := tr.G1FromProto(key.Ipk.HRand) + if err != nil { + t.Logf("G1FromProto returned error: %s", err) + t.Fail() + return + } + Nym_eid := H_a_eid.Mul2(attrs[eidIndex], HRand, meta.EidNymAuditData.Rand) + EidNym, err := tr.G1FromProto(sig.EidNym.Nym) + if err != nil { + t.Logf("G1FromProto returned error: %s", err) + t.Fail() + return + } + require.True(t, Nym_eid.Equals(EidNym)) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Logf("Signature should be valid but verification returned error: %s", err) + t.Fail() + return + } + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.Error(t, err) + require.Equal(t, "EidNym available but ExpectStandard required", err.Error()) + + // supply the meta to audit the nym eid + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.NoError(t, err) + // tamper with the randomness of the nym eid to expect a failed verification + meta.EidNymAuditData.Attr = curve.NewZrFromInt(35) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + + // Test signing selective disclosure + disclosure = []byte{0, 1, 1, 1, 0} + sig, _, err = idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, 0, cri, rng, tr, opts.Standard, nil) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + require.Equal(t, "no EidNym provided but ExpectEidNym required", err.Error()) + }() + } + + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + rng, _ := curve.Rand() + + disclosure := []byte{0, 0, 0, 0, 0} + msg := []byte{1, 2, 3, 4, 5} + rhindex := 4 + + eidIndex := 2 + sig, meta, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, eidIndex, cri, rng, tr, opts.EidNym, nil) + require.NoError(t, err) + + // assert that the returned randomness is the right one + H_a_eid, err := tr.G1FromProto(key.Ipk.HAttrs[eidIndex]) + require.NoError(t, err) + HRand, err := tr.G1FromProto(key.Ipk.HRand) + require.NoError(t, err) + Nym_eid := H_a_eid.Mul2(attrs[eidIndex], HRand, meta.EidNymAuditData.Rand) + EidNym, err := tr.G1FromProto(sig.EidNym.Nym) + require.NoError(t, err) + require.True(t, Nym_eid.Equals(EidNym)) + + // and now do it with the function + err = sig.AuditNymEid(key.Ipk, attrs[eidIndex], eidIndex, meta.EidNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = NymEID(meta.EidNymAuditData.Nym.Bytes()).AuditNymEid(key.Ipk, attrs[eidIndex], eidIndex, meta.EidNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Logf("Signature should be valid but verification returned error: %s", err) + t.Fail() + return + } + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.Error(t, err) + require.Equal(t, "EidNym available but ExpectStandard required", err.Error()) + + // supply the meta to audit the nym eid + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.NoError(t, err) + // tamper with the randomness of the nym eid to expect a failed verification + meta.EidNymAuditData.Attr = curve.NewZrFromInt(35) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, 0, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + }() + } + + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + rng, _ := curve.Rand() + + disclosure := []byte{0, 0, 0, 0, 0} + msg := []byte{1, 2, 3, 4, 5} + rhindex := 4 + + eidIndex := 2 + sig, meta, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, eidIndex, cri, rng, tr, opts.EidNymRhNym, nil) + require.NoError(t, err) + + // assert that the returned randomness is the right one + H_a_eid, err := tr.G1FromProto(key.Ipk.HAttrs[eidIndex]) + require.NoError(t, err) + HRand, err := tr.G1FromProto(key.Ipk.HRand) + require.NoError(t, err) + Nym_eid := H_a_eid.Mul2(attrs[eidIndex], HRand, meta.EidNymAuditData.Rand) + EidNym, err := tr.G1FromProto(sig.EidNym.Nym) + require.NoError(t, err) + require.True(t, Nym_eid.Equals(EidNym)) + + // and now do it with the function + err = sig.AuditNymEid(key.Ipk, attrs[eidIndex], eidIndex, meta.EidNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = NymEID(meta.EidNymAuditData.Nym.Bytes()).AuditNymEid(key.Ipk, attrs[eidIndex], eidIndex, meta.EidNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = sig.AuditNymRh(key.Ipk, attrs[rhindex], rhindex, meta.RhNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = NymRH(meta.RhNymAuditData.Nym.Bytes()).AuditNymRh(key.Ipk, attrs[rhindex], rhindex, meta.RhNymAuditData.Rand, idmx.Curve, tr) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, nil, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + if err != nil { + t.Logf("Signature should be valid but verification returned error: %s", err) + t.Fail() + return + } + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNymRhNym, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.Error(t, err) + require.Equal(t, "RhNym available but ExpectStandard required", err.Error()) + + // supply the meta to audit the nym eid and rh + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNymRhNym, meta) + require.NoError(t, err) + + // tamper with the randomness of the nym eid to expect a failed verification + tmp := meta.EidNymAuditData.Attr + meta.EidNymAuditData.Attr = curve.NewZrFromInt(35) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNymRhNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym eid validation failed, does not match regenerated nym eid", err.Error()) + meta.EidNymAuditData.Attr = tmp + + // tamper with the randomness of the nym rh to expect a failed verification + meta.RhNymAuditData.Attr = curve.NewZrFromInt(35) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym rh validation failed, does not match regenerated nym rh", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNymRhNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: nym rh validation failed, does not match regenerated nym rh", err.Error()) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, eidIndex, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, meta) + require.Error(t, err) + require.Equal(t, "signature invalid: zero-knowledge proof is invalid", err.Error()) + }() + } + + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + rng, _ := curve.Rand() + + msg := []byte{1, 2, 3, 4, 5} + rhindex := 4 + + // Test signing selective disclosure + disclosure := []byte{0, 1, 1, 1, 0} + sig, _, err := idmx.NewSignature(cred, sk, Nym, RandNym, key.Ipk, disclosure, msg, rhindex, 0, cri, rng, tr, opts.Standard, nil) + require.NoError(t, err) + + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.BestEffort, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectStandard, nil) + require.NoError(t, err) + err = sig.Ver(disclosure, key.Ipk, msg, attrs, rhindex, 2, &revocationKey.PublicKey, epoch, idmx.Curve, tr, opts.ExpectEidNym, nil) + require.Error(t, err) + require.Equal(t, "no EidNym provided but ExpectEidNym required", err.Error()) + }() + } + + waitGroup.Wait() +} + +func TestNymSigParallelAMCL(t *testing.T) { + t.Parallel() + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + testNymSigParallel( + t, + curve, + &amcl.Fp256bnMiracl{C: curve}, + ) +} + +func TestNymSigParallelGurvy254(t *testing.T) { + t.Parallel() + curve := math.Curves[math.BN254] + testNymSigParallel( + t, + curve, + &amcl.Gurvy{C: curve}, + ) +} + +func testNymSigParallel(t *testing.T, curve *math.Curve, tr Translator) { + idmx := &Idemix{ + Curve: curve, + } + // Test weak BB sigs: + // Test KeyGen + rng, err := curve.Rand() + require.NoError(t, err) + + // Test idemix functionality + AttributeNames := []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + attrs := make([]*math.Zr, len(AttributeNames)) + for i := range AttributeNames { + attrs[i] = curve.NewZrFromInt(int64(i)) + } + + // Create a new key pair + key, err := idmx.NewIssuerKey(AttributeNames, rng, tr) + require.NoError(t, err) + + // Check that the key is valid + err = key.GetIpk().Check(curve, tr) + require.NoError(t, err) + + // Test issuance + sk := curve.NewRandomZr(rng) + ni := curve.NewRandomZr(rng) + m, err := idmx.NewCredRequest(sk, ni.Bytes(), key.Ipk, rng, tr) + require.NoError(t, err) + + cred, err := idmx.NewCredential(key, m, attrs, rng, tr) + require.NoError(t, err, "Failed to issue a credential: \"%s\"", err) + require.NoError(t, cred.Ver(sk, key.Ipk, idmx.Curve, tr), "credential should be valid") + + // Generate a revocation key pair + revocationKey, err := idmx.GenerateLongTermRevocationKey() + require.NoError(t, err) + + // Create CRI that contains no revocation mechanism + epoch := 0 + cri, err := idmx.CreateCRI(revocationKey, []*math.Zr{}, epoch, ALG_NO_REVOCATION, rng, tr) + require.NoError(t, err) + err = idmx.VerifyEpochPK(&revocationKey.PublicKey, cri.EpochPk, cri.EpochPkSig, int(cri.Epoch), RevocationAlgorithm(cri.RevocationAlg)) + require.NoError(t, err) + + Nym, RandNym, err := idmx.MakeNym(sk, key.Ipk, rng, tr) + require.NoError(t, err) + + waitGroup := &sync.WaitGroup{} + n := 100 + + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + rng, _ := curve.Rand() + + // Test NymSignatures + nymsig, err := idmx.NewNymSignature(sk, Nym, RandNym, key.Ipk, []byte("testing"), rng, tr) + require.NoError(t, err) + + err = nymsig.Ver(Nym, key.Ipk, []byte("testing"), idmx.Curve, tr) + if err != nil { + t.Logf("NymSig should be valid but verification returned error: %s", err) + t.Fail() + return + } + }() + } + + waitGroup.Wait() +} + +func TestIPKCheckAMCL(t *testing.T) { + t.Parallel() + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + testIPKCheck( + t, + curve, + &amcl.Fp256bnMiracl{C: curve}, + ) +} + +func TestIPKCheckGurvy254(t *testing.T) { + t.Parallel() + curve := math.Curves[math.BN254] + testIPKCheck( + t, + curve, + &amcl.Gurvy{C: curve}, + ) +} + +func testIPKCheck(t *testing.T, curve *math.Curve, tr Translator) { + idmx := &Idemix{ + Curve: curve, + } + waitGroup := &sync.WaitGroup{} + n := 50 + waitGroup.Add(n) + for i := 0; i < n; i++ { + go func() { + defer waitGroup.Done() + // Test weak BB sigs: + // Test KeyGen + rng, err := curve.Rand() + require.NoError(t, err) + + // Test idemix functionality + AttributeNames := []string{"Attr1", "Attr2", "Attr3", "Attr4", "Attr5"} + attrs := make([]*math.Zr, len(AttributeNames)) + for i := range AttributeNames { + attrs[i] = curve.NewZrFromInt(int64(i)) + } + + // Create a new key pair + key, err := idmx.NewIssuerKey(AttributeNames, rng, tr) + require.NoError(t, err) + + // Check that the key is valid + err = key.GetIpk().Check(curve, tr) + require.NoError(t, err) + }() + } + waitGroup.Wait() +} diff --git a/v2/bccsp/schemes/dlog/crypto/issuerkey.go b/v2/bccsp/schemes/dlog/crypto/issuerkey.go new file mode 100644 index 0000000..eddbb08 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/issuerkey.go @@ -0,0 +1,231 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// The Issuer secret ISk and public IPk keys are used to issue credentials and +// to verify signatures created using the credentials + +// The Issuer Secret Key is a random exponent (generated randomly from Z*_p) + +// The Issuer Public Key consists of several elliptic curve points (ECP), +// where index 1 corresponds to group G1 and 2 to group G2) +// HSk, HRand, BarG1, BarG2, an ECP array HAttrs, and an ECP2 W, +// and a proof of knowledge of the corresponding secret key + +// NewIssuerKey creates a new issuer key pair taking an array of attribute names +// that will be contained in credentials certified by this issuer (a credential specification) +// See http://eprint.iacr.org/2016/663.pdf Sec. 4.3, for references. +func (i *Idemix) NewIssuerKey(AttributeNames []string, rng io.Reader, t Translator) (*IssuerKey, error) { + return newIssuerKey(AttributeNames, rng, i.Curve, t) +} + +func newIssuerKey(AttributeNames []string, rng io.Reader, curve *math.Curve, t Translator) (*IssuerKey, error) { + // validate inputs + + // check for duplicated attributes + attributeNamesMap := map[string]bool{} + for _, name := range AttributeNames { + if attributeNamesMap[name] { + return nil, errors.Errorf("attribute %s appears multiple times in AttributeNames", name) + } + attributeNamesMap[name] = true + } + + key := new(IssuerKey) + + // generate issuer secret key + ISk := curve.NewRandomZr(rng) + key.Isk = ISk.Bytes() + + // generate the corresponding public key + key.Ipk = new(IssuerPublicKey) + key.Ipk.AttributeNames = AttributeNames + + W := curve.GenG2.Mul(ISk) + key.Ipk.W = t.G2ToProto(W) + + // generate bases that correspond to the attributes + key.Ipk.HAttrs = make([]*amcl.ECP, len(AttributeNames)) + for i := 0; i < len(AttributeNames); i++ { + key.Ipk.HAttrs[i] = t.G1ToProto(curve.GenG1.Mul(curve.NewRandomZr(rng))) + } + + // generate base for the secret key + HSk := curve.GenG1.Mul(curve.NewRandomZr(rng)) + key.Ipk.HSk = t.G1ToProto(HSk) + + // generate base for the randomness + HRand := curve.GenG1.Mul(curve.NewRandomZr(rng)) + key.Ipk.HRand = t.G1ToProto(HRand) + + BarG1 := curve.GenG1.Mul(curve.NewRandomZr(rng)) + key.Ipk.BarG1 = t.G1ToProto(BarG1) + + BarG2 := BarG1.Mul(ISk) + key.Ipk.BarG2 = t.G1ToProto(BarG2) + + // generate a zero-knowledge proof of knowledge (ZK PoK) of the secret key which + // is in W and BarG2. + + // Sample the randomness needed for the proof + r := curve.NewRandomZr(rng) + + // Step 1: First message (t-values) + t1 := curve.GenG2.Mul(r) // t1 = g_2^r, cover W + t2 := BarG1.Mul(r) // t2 = (\bar g_1)^r, cover BarG2 + + // Step 2: Compute the Fiat-Shamir hash, forming the challenge of the ZKP. + proofData := make([]byte, 3*curve.G1ByteSize+3*curve.G2ByteSize) + index := 0 + index = appendBytesG2(proofData, index, t1) + index = appendBytesG1(proofData, index, t2) + index = appendBytesG2(proofData, index, curve.GenG2) + index = appendBytesG1(proofData, index, BarG1) + index = appendBytesG2(proofData, index, W) + index = appendBytesG1(proofData, index, BarG2) + + proofC := curve.HashToZr(proofData) + key.Ipk.ProofC = proofC.Bytes() + + // Step 3: reply to the challenge message (s-values) + proofS := curve.ModAdd(curve.ModMul(proofC, ISk, curve.GroupOrder), r, curve.GroupOrder) // // s = r + C \cdot ISk + key.Ipk.ProofS = proofS.Bytes() + + // Hash the public key + serializedIPk, err := proto.Marshal(key.Ipk) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal issuer public key") + } + key.Ipk.Hash = curve.HashToZr(serializedIPk).Bytes() + + // We are done + return key, nil +} + +func (i *Idemix) NewIssuerKeyFromBytes(raw []byte) (*IssuerKey, error) { + return newIssuerKeyFromBytes(raw) +} + +func newIssuerKeyFromBytes(raw []byte) (*IssuerKey, error) { + ik := &IssuerKey{} + if err := proto.Unmarshal(raw, ik); err != nil { + return nil, err + } + + // raw, err :=proto.Marshal(ik.Ipk.W) + // if err != nil { + // panic(err) + // } + // fmt.Printf("IPKW : [%v]", ik.Ipk.W.Xa) + + return ik, nil +} + +// Check checks that this issuer public key is valid, i.e. +// that all components are present and a ZK proofs verifies +func (IPk *IssuerPublicKey) Check(curve *math.Curve, t Translator) error { + // Unmarshall the public key + NumAttrs := len(IPk.GetAttributeNames()) + HSk, err := t.G1FromProto(IPk.GetHSk()) + if err != nil { + return err + } + + HRand, err := t.G1FromProto(IPk.GetHRand()) + if err != nil { + return err + } + + HAttrs := make([]*math.G1, len(IPk.GetHAttrs())) + for i := 0; i < len(IPk.GetHAttrs()); i++ { + HAttrs[i], err = t.G1FromProto(IPk.GetHAttrs()[i]) + if err != nil { + return err + } + } + BarG1, err := t.G1FromProto(IPk.GetBarG1()) + if err != nil { + return err + } + + BarG2, err := t.G1FromProto(IPk.GetBarG2()) + if err != nil { + return err + } + + W, err := t.G2FromProto(IPk.GetW()) + if err != nil { + return err + } + + ProofC := curve.NewZrFromBytes(IPk.GetProofC()) + ProofS := curve.NewZrFromBytes(IPk.GetProofS()) + + // Check that the public key is well-formed + if NumAttrs < 0 || + HSk == nil || + HRand == nil || + BarG1 == nil || + BarG1.IsInfinity() || + BarG2 == nil || + HAttrs == nil || + len(IPk.HAttrs) < NumAttrs { + return errors.Errorf("some part of the public key is undefined") + } + for i := 0; i < NumAttrs; i++ { + if IPk.HAttrs[i] == nil { + return errors.Errorf("some part of the public key is undefined") + } + } + + // Verify Proof + + // Recompute challenge + proofData := make([]byte, 3*curve.G1ByteSize+3*curve.G2ByteSize) + index := 0 + + // Recompute t-values using s-values + t1 := curve.GenG2.Mul(ProofS) + t1.Add(W.Mul(curve.ModNeg(ProofC, curve.GroupOrder))) // t1 = g_2^s \cdot W^{-C} + + t2 := BarG1.Mul(ProofS) + t2.Add(BarG2.Mul(curve.ModNeg(ProofC, curve.GroupOrder))) // t2 = {\bar g_1}^s \cdot {\bar g_2}^C + + index = appendBytesG2(proofData, index, t1) + index = appendBytesG1(proofData, index, t2) + index = appendBytesG2(proofData, index, curve.GenG2) + index = appendBytesG1(proofData, index, BarG1) + index = appendBytesG2(proofData, index, W) + index = appendBytesG1(proofData, index, BarG2) + + // Verify that the challenge is the same + if !ProofC.Equals(curve.HashToZr(proofData)) { + return errors.Errorf("zero knowledge proof in public key invalid") + } + + return IPk.SetHash(curve) +} + +// SetHash appends a hash of a serialized public key +func (IPk *IssuerPublicKey) SetHash(curve *math.Curve) error { + IPk.Hash = nil + serializedIPk, err := proto.Marshal(IPk) + if err != nil { + return errors.Wrap(err, "Failed to marshal issuer public key") + } + IPk.Hash = curve.HashToZr(serializedIPk).Bytes() + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/logging.go b/v2/bccsp/schemes/dlog/crypto/logging.go new file mode 100644 index 0000000..7feb99d --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/logging.go @@ -0,0 +1,37 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import "log" + +// logger is used to log debug information. +var logger Logger = LogFunc(log.Printf) + +// SetLogger sets the logger instance used for debug and error reporting. The +// logger reference is not mutex-protected so this must be set before calling +// any other library functions. +// +// If a custom logger is not defined, the global logger from the standard +// library's log package is used. +func SetLogger(l Logger) { + logger = l +} + +// Logger defines the contract for logging. This interface is explicitly +// defined to be compatible with the logger in the standard library log +// package. +type Logger interface { + Printf(format string, a ...interface{}) +} + +// LogFunc is a function adapter for logging. +type LogFunc func(format string, a ...interface{}) + +// Printf is used to create a formatted string log record. +func (l LogFunc) Printf(format string, a ...interface{}) { + l(format, a...) +} diff --git a/v2/bccsp/schemes/dlog/crypto/nonrevocation-prover.go b/v2/bccsp/schemes/dlog/crypto/nonrevocation-prover.go new file mode 100644 index 0000000..c9146b5 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/nonrevocation-prover.go @@ -0,0 +1,47 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// nonRevokedProver is the Prover of the ZK proof system that handles revocation. +type nonRevokedProver interface { + // getFSContribution returns the non-revocation contribution to the Fiat-Shamir hash, forming the challenge of the ZKP, + getFSContribution(rh *math.Zr, rRh *math.Zr, cri *CredentialRevocationInformation, rng io.Reader) ([]byte, error) + + // getNonRevokedProof returns a proof of non-revocation with the respect to passed challenge + getNonRevokedProof(chal *math.Zr) (*NonRevocationProof, error) +} + +// nopNonRevokedProver is an empty nonRevokedProver +type nopNonRevokedProver struct{} + +func (prover *nopNonRevokedProver) getFSContribution(rh *math.Zr, rRh *math.Zr, cri *CredentialRevocationInformation, rng io.Reader) ([]byte, error) { + return nil, nil +} + +func (prover *nopNonRevokedProver) getNonRevokedProof(chal *math.Zr) (*NonRevocationProof, error) { + ret := &NonRevocationProof{} + ret.RevocationAlg = int32(ALG_NO_REVOCATION) + return ret, nil +} + +// getNonRevocationProver returns the nonRevokedProver bound to the passed revocation algorithm +func getNonRevocationProver(algorithm RevocationAlgorithm) (nonRevokedProver, error) { + switch algorithm { + case ALG_NO_REVOCATION: + return &nopNonRevokedProver{}, nil + default: + // unknown revocation algorithm + return nil, errors.Errorf("unknown revocation algorithm %d", algorithm) + } +} diff --git a/v2/bccsp/schemes/dlog/crypto/nonrevocation-verifier.go b/v2/bccsp/schemes/dlog/crypto/nonrevocation-verifier.go new file mode 100644 index 0000000..a018aa5 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/nonrevocation-verifier.go @@ -0,0 +1,36 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// nonRevokedProver is the Verifier of the ZK proof system that handles revocation. +type nonRevocationVerifier interface { + // recomputeFSContribution recomputes the contribution of the non-revocation proof to the ZKP challenge + recomputeFSContribution(proof *NonRevocationProof, chal *math.Zr, epochPK *math.G2, proofSRh *math.Zr) ([]byte, error) +} + +// nopNonRevocationVerifier is an empty nonRevocationVerifier that produces an empty contribution +type nopNonRevocationVerifier struct{} + +func (verifier *nopNonRevocationVerifier) recomputeFSContribution(proof *NonRevocationProof, chal *math.Zr, epochPK *math.G2, proofSRh *math.Zr) ([]byte, error) { + return nil, nil +} + +// getNonRevocationVerifier returns the nonRevocationVerifier bound to the passed revocation algorithm +func getNonRevocationVerifier(algorithm RevocationAlgorithm) (nonRevocationVerifier, error) { + switch algorithm { + case ALG_NO_REVOCATION: + return &nopNonRevocationVerifier{}, nil + default: + // unknown revocation algorithm + return nil, errors.Errorf("unknown revocation algorithm %d", algorithm) + } +} diff --git a/v2/bccsp/schemes/dlog/crypto/nymeid.go b/v2/bccsp/schemes/dlog/crypto/nymeid.go new file mode 100644 index 0000000..c0131ba --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/nymeid.go @@ -0,0 +1,59 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +type NymEID []byte + +func (nym NymEID) AuditNymEid( + ipk *IssuerPublicKey, + eidAttr *math.Zr, + eidIndex int, + RNymEid *math.Zr, + curve *math.Curve, + t Translator, +) error { + // Validate inputs + if ipk == nil { + return errors.Errorf("cannot verify idemix signature: received nil input") + } + + if len(nym) == 0 { + return errors.Errorf("no EidNym provided") + } + + if len(ipk.HAttrs) <= eidIndex { + return errors.Errorf("could not access H_a_eid in array") + } + + H_a_eid, err := t.G1FromProto(ipk.HAttrs[eidIndex]) + if err != nil { + return errors.Wrap(err, "could not deserialize H_a_eid") + } + + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return errors.Wrap(err, "could not deserialize HRand") + } + + EidNym, err := curve.NewG1FromBytes(nym) + if err != nil { + return errors.Wrap(err, "could not deserialize EidNym") + } + + Nym_eid := H_a_eid.Mul2(eidAttr, HRand, RNymEid) + + if !Nym_eid.Equals(EidNym) { + return errors.New("eid nym does not match") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/nymrh.go b/v2/bccsp/schemes/dlog/crypto/nymrh.go new file mode 100644 index 0000000..11017ac --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/nymrh.go @@ -0,0 +1,59 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +type NymRH []byte + +func (nym NymRH) AuditNymRh( + ipk *IssuerPublicKey, + rhAttr *math.Zr, + rhIndex int, + RNymRh *math.Zr, + curve *math.Curve, + t Translator, +) error { + // Validate inputs + if ipk == nil { + return errors.Errorf("cannot verify idemix signature: received nil input") + } + + if len(nym) == 0 { + return errors.Errorf("no RhNym provided") + } + + if len(ipk.HAttrs) <= rhIndex { + return errors.Errorf("could not access H_a_rh in array") + } + + H_a_rh, err := t.G1FromProto(ipk.HAttrs[rhIndex]) + if err != nil { + return errors.Wrap(err, "could not deserialize H_a_rh") + } + + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return errors.Wrap(err, "could not deserialize HRand") + } + + RhNym, err := curve.NewG1FromBytes(nym) + if err != nil { + return errors.Wrap(err, "could not deserialize RhNym") + } + + Nym_rh := H_a_rh.Mul2(rhAttr, HRand, RNymRh) + + if !Nym_rh.Equals(RhNym) { + return errors.New("rh nym does not match") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/nymsignature.go b/v2/bccsp/schemes/dlog/crypto/nymsignature.go new file mode 100644 index 0000000..3e40fbc --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/nymsignature.go @@ -0,0 +1,128 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// NewSignature creates a new idemix pseudonym signature +func (i *Idemix) NewNymSignature(sk *math.Zr, Nym *math.G1, RNym *math.Zr, ipk *IssuerPublicKey, msg []byte, rng io.Reader, tr Translator) (*NymSignature, error) { + return newNymSignature(sk, Nym, RNym, ipk, msg, rng, i.Curve, tr) +} + +func newNymSignature(sk *math.Zr, Nym *math.G1, RNym *math.Zr, ipk *IssuerPublicKey, msg []byte, rng io.Reader, curve *math.Curve, tr Translator) (*NymSignature, error) { + // Validate inputs + if sk == nil || Nym == nil || RNym == nil || ipk == nil || rng == nil { + return nil, errors.Errorf("cannot create NymSignature: received nil input") + } + + Nonce := curve.NewRandomZr(rng) + + HRand, err := tr.G1FromProto(ipk.HRand) + if err != nil { + return nil, err + } + + HSk, err := tr.G1FromProto(ipk.HSk) + if err != nil { + return nil, err + } + + // The rest of this function constructs the non-interactive zero knowledge proof proving that + // the signer 'owns' this pseudonym, i.e., it knows the secret key and randomness on which it is based. + // Recall that (Nym,RNym) is the output of MakeNym. Therefore, Nym = h_{sk}^sk \cdot h_r^r + + // Sample the randomness needed for the proof + rSk := curve.NewRandomZr(rng) + rRNym := curve.NewRandomZr(rng) + + // Step 1: First message (t-values) + t := HSk.Mul2(rSk, HRand, rRNym) // t = h_{sk}^{r_sk} \cdot h_r^{r_{RNym} + + // Step 2: Compute the Fiat-Shamir hash, forming the challenge of the ZKP. + // proofData will hold the data being hashed, it consists of: + // - the signature label + // - 2 elements of G1 each taking 2*math.FieldBytes+1 bytes + // - one bigint (hash of the issuer public key) of length math.FieldBytes + // - disclosed attributes + // - message being signed + proofData := make([]byte, len([]byte(signLabel))+2*curve.G1ByteSize+curve.ScalarByteSize+len(msg)) + index := 0 + index = appendBytesString(proofData, index, signLabel) + index = appendBytesG1(proofData, index, t) + index = appendBytesG1(proofData, index, Nym) + copy(proofData[index:], ipk.Hash) + index = index + curve.ScalarByteSize + copy(proofData[index:], msg) + c := curve.HashToZr(proofData) + // combine the previous hash and the nonce and hash again to compute the final Fiat-Shamir value 'ProofC' + index = 0 + proofData = proofData[:2*curve.ScalarByteSize] + index = appendBytesBig(proofData, index, c) + appendBytesBig(proofData, index, Nonce) + ProofC := curve.HashToZr(proofData) + + // Step 3: reply to the challenge message (s-values) + ProofSSk := curve.ModAdd(rSk, curve.ModMul(ProofC, sk, curve.GroupOrder), curve.GroupOrder) // s_{sk} = r_{sk} + C \cdot sk + ProofSRNym := curve.ModAdd(rRNym, curve.ModMul(ProofC, RNym, curve.GroupOrder), curve.GroupOrder) // s_{RNym} = r_{RNym} + C \cdot RNym + + // The signature consists of the Fiat-Shamir hash (ProofC), the s-values (ProofSSk, ProofSRNym), and the nonce. + return &NymSignature{ + ProofC: ProofC.Bytes(), + ProofSSk: ProofSSk.Bytes(), + ProofSRNym: ProofSRNym.Bytes(), + Nonce: Nonce.Bytes()}, nil +} + +// Ver verifies an idemix NymSignature +func (sig *NymSignature) Ver(nym *math.G1, ipk *IssuerPublicKey, msg []byte, curve *math.Curve, tr Translator) error { + ProofC := curve.NewZrFromBytes(sig.GetProofC()) + ProofSSk := curve.NewZrFromBytes(sig.GetProofSSk()) + ProofSRNym := curve.NewZrFromBytes(sig.GetProofSRNym()) + Nonce := curve.NewZrFromBytes(sig.GetNonce()) + + HRand, err := tr.G1FromProto(ipk.HRand) + if err != nil { + return err + } + + HSk, err := tr.G1FromProto(ipk.HSk) + if err != nil { + return err + } + + // Verify Proof + + // Recompute t-values using s-values + t := HSk.Mul2(ProofSSk, HRand, ProofSRNym) + t.Sub(nym.Mul(ProofC)) // t = h_{sk}^{s_{sk} \ cdot h_r^{s_{RNym} + + // Recompute challenge + proofData := make([]byte, len([]byte(signLabel))+2*curve.G1ByteSize+curve.ScalarByteSize+len(msg)) + index := 0 + index = appendBytesString(proofData, index, signLabel) + index = appendBytesG1(proofData, index, t) + index = appendBytesG1(proofData, index, nym) + copy(proofData[index:], ipk.Hash) + index = index + curve.ScalarByteSize + copy(proofData[index:], msg) + c := curve.HashToZr(proofData) + index = 0 + proofData = proofData[:2*curve.ScalarByteSize] + index = appendBytesBig(proofData, index, c) + appendBytesBig(proofData, index, Nonce) + + if !ProofC.Equals(curve.HashToZr(proofData)) { + return errors.Errorf("pseudonym signature invalid: zero-knowledge proof is invalid") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/revocation_authority.go b/v2/bccsp/schemes/dlog/crypto/revocation_authority.go new file mode 100644 index 0000000..7bf7e0b --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/revocation_authority.go @@ -0,0 +1,136 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "io" + "math/big" + + weakbb "github.com/IBM/idemix/bccsp/schemes/weak-bb" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +type RevocationAlgorithm int32 + +const ( + ALG_NO_REVOCATION RevocationAlgorithm = iota +) + +var ProofBytes = map[RevocationAlgorithm]int{ + ALG_NO_REVOCATION: 0, +} + +// GenerateLongTermRevocationKey generates a long term signing key that will be used for revocation +func (i *Idemix) GenerateLongTermRevocationKey() (*ecdsa.PrivateKey, error) { + return generateLongTermRevocationKey() +} + +func generateLongTermRevocationKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) +} + +// GenerateLongTermRevocationKey generates a long term signing key that will be used for revocation +func (i *Idemix) LongTermRevocationKeyFromBytes(raw []byte) (*ecdsa.PrivateKey, error) { + return longTermRevocationKeyFromBytes(raw) +} + +func longTermRevocationKeyFromBytes(raw []byte) (*ecdsa.PrivateKey, error) { + priv := &ecdsa.PrivateKey{} + priv.D = new(big.Int).SetBytes(raw) + priv.PublicKey.Curve = elliptic.P384() + priv.PublicKey.X, priv.PublicKey.Y = elliptic.P384().ScalarBaseMult(priv.D.Bytes()) + + return priv, nil +} + +// CreateCRI creates the Credential Revocation Information for a certain time period (epoch). +// Users can use the CRI to prove that they are not revoked. +// Note that when not using revocation (i.e., alg = ALG_NO_REVOCATION), the entered unrevokedHandles are not used, +// and the resulting CRI can be used by any signer. +func (i *Idemix) CreateCRI(key *ecdsa.PrivateKey, unrevokedHandles []*math.Zr, epoch int, alg RevocationAlgorithm, rng io.Reader, t Translator) (*CredentialRevocationInformation, error) { + return createCRI(key, unrevokedHandles, epoch, alg, rng, i.Curve, t) +} + +func createCRI(key *ecdsa.PrivateKey, unrevokedHandles []*math.Zr, epoch int, alg RevocationAlgorithm, rng io.Reader, curve *math.Curve, t Translator) (*CredentialRevocationInformation, error) { + if key == nil || rng == nil { + return nil, errors.Errorf("CreateCRI received nil input") + } + cri := &CredentialRevocationInformation{} + cri.RevocationAlg = int32(alg) + cri.Epoch = int64(epoch) + + if alg == ALG_NO_REVOCATION { + // put a dummy PK in the proto + cri.EpochPk = t.G2ToProto(curve.GenG2) + } else { + // create epoch key + _, epochPk := weakbb.WbbKeyGen(curve, rng) + cri.EpochPk = t.G2ToProto(epochPk) + } + + // sign epoch + epoch key with long term key + bytesToSign, err := proto.Marshal(cri) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal CRI") + } + + digest := sha256.Sum256(bytesToSign) + + cri.EpochPkSig, err = key.Sign(rand.Reader, digest[:], nil) + if err != nil { + return nil, err + } + + if alg == ALG_NO_REVOCATION { + return cri, nil + } else { + return nil, errors.Errorf("the specified revocation algorithm is not supported.") + } +} + +// VerifyEpochPK verifies that the revocation PK for a certain epoch is valid, +// by checking that it was signed with the long term revocation key. +// Note that even if we use no revocation (i.e., alg = ALG_NO_REVOCATION), we need +// to verify the signature to make sure the issuer indeed signed that no revocation +// is used in this epoch. +func (i *Idemix) VerifyEpochPK(pk *ecdsa.PublicKey, epochPK *amcl.ECP2, epochPkSig []byte, epoch int, alg RevocationAlgorithm) error { + return verifyEpochPK(pk, epochPK, epochPkSig, epoch, alg) +} + +func verifyEpochPK(pk *ecdsa.PublicKey, epochPK *amcl.ECP2, epochPkSig []byte, epoch int, alg RevocationAlgorithm) error { + if pk == nil || epochPK == nil { + return errors.Errorf("EpochPK invalid: received nil input") + } + cri := &CredentialRevocationInformation{} + cri.RevocationAlg = int32(alg) + cri.EpochPk = epochPK + cri.Epoch = int64(epoch) + bytesToSign, err := proto.Marshal(cri) + if err != nil { + return err + } + digest := sha256.Sum256(bytesToSign) + + var sig struct{ R, S *big.Int } + if _, err := asn1.Unmarshal(epochPkSig, &sig); err != nil { + return errors.Wrap(err, "failed unmashalling signature") + } + + if !ecdsa.Verify(pk, digest[:], sig.R, sig.S) { + return errors.Errorf("EpochPKSig invalid") + } + + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/signature.go b/v2/bccsp/schemes/dlog/crypto/signature.go new file mode 100644 index 0000000..9312876 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/signature.go @@ -0,0 +1,1190 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "crypto/ecdsa" + "fmt" + "io" + "sort" + + opts "github.com/IBM/idemix/bccsp/types" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// signLabel is the label used in zero-knowledge proof (ZKP) to identify that this ZKP is a signature of knowledge +const signLabel = "sign" +const signWithEidNymLabel = "signWithEidNym" +const signWithEidNymRhNymLabel = "signWithEidNymRhNym" // When the revocation handle is present the enrollment id must also be present + +// A signature that is produced using an Identity Mixer credential is a so-called signature of knowledge +// (for details see C.P.Schnorr "Efficient Identification and Signatures for Smart Cards") +// An Identity Mixer signature is a signature of knowledge that signs a message and proves (in zero-knowledge) +// the knowledge of the user secret (and possibly attributes) signed inside a credential +// that was issued by a certain issuer (referred to with the issuer public key) +// The signature is verified using the message being signed and the public key of the issuer +// Some of the attributes from the credential can be selectively disclosed or different statements can be proven about +// credential attributes without disclosing them in the clear +// The difference between a standard signature using X.509 certificates and an Identity Mixer signature is +// the advanced privacy features provided by Identity Mixer (due to zero-knowledge proofs): +// - Unlinkability of the signatures produced with the same credential +// - Selective attribute disclosure and predicates over attributes + +// Make a slice of all the attribute indices that will not be disclosed +func hiddenIndices(Disclosure []byte) []int { + HiddenIndices := make([]int, 0) + for index, disclose := range Disclosure { + if disclose == 0 { + HiddenIndices = append(HiddenIndices, index) + } + } + return HiddenIndices +} + +// NewSignature creates a new idemix signature (Schnorr-type signature) +// The []byte Disclosure steers which attributes are disclosed: +// if Disclosure[i] == 0 then attribute i remains hidden and otherwise it is disclosed. +// We require the revocation handle to remain undisclosed (i.e., Disclosure[rhIndex] == 0). +// We use the zero-knowledge proof by http://eprint.iacr.org/2016/663.pdf, Sec. 4.5 to prove knowledge of a BBS+ signature +func (i *Idemix) NewSignature( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure, msg []byte, + rhIndex, eidIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + tr Translator, + sigType opts.SignatureType, + metadata *opts.IdemixSignerMetadata, +) (*Signature, *opts.IdemixSignerMetadata, error) { + switch sigType { + case opts.Standard: // Generation of standard signature + return newSignature(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, cri, rng, i.Curve, tr, metadata) + case opts.EidNym: // Generation of pseudonymous eid signature + return newSignatureWithEIDNym(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, eidIndex, cri, rng, i.Curve, tr, metadata) + case opts.EidNymRhNym: // Generation of pseudonymous eid and rh signature + return newSignatureWithEIDNymAndRHNym(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, eidIndex, cri, rng, i.Curve, tr, metadata) + } + + panic(fmt.Sprintf("programming error, requested signature type %d", sigType)) +} + +func newSignature( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure []byte, + msg []byte, + rhIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + curve *math.Curve, + tr Translator, + metadata *opts.IdemixSignerMetadata, +) (*Signature, *opts.IdemixSignerMetadata, error) { + t1, t2, t3, APrime, ABar, BPrime, nonRevokedProofHashData, E, Nonce, rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, rAttrs, prover, HiddenIndices, err := prepare(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, cri, rng, curve, tr) + if err != nil { + return nil, nil, err + } + + return finalise( + cred, + sk, + Nym, + RNym, + ipk, + Disclosure, + msg, + rhIndex, -1, + cri, + rng, + curve, + tr, + t1, t2, t3, + APrime, ABar, BPrime, + nonRevokedProofHashData, + E, + Nonce, + rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, + rAttrs, + prover, + HiddenIndices, + opts.Standard, + metadata, + ) +} + +func newSignatureWithEIDNym( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure []byte, + msg []byte, + rhIndex, eidIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + curve *math.Curve, + tr Translator, + metadata *opts.IdemixSignerMetadata, +) (*Signature, *opts.IdemixSignerMetadata, error) { + if Disclosure[eidIndex] != 0 { + return nil, nil, errors.Errorf("cannot create idemix signature: disclosure of enrollment ID requested for NewSignatureWithEIDNym") + } + + t1, t2, t3, APrime, ABar, BPrime, nonRevokedProofHashData, E, Nonce, rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, rAttrs, prover, HiddenIndices, err := prepare(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, cri, rng, curve, tr) + if err != nil { + return nil, nil, err + } + + return finalise( + cred, + sk, + Nym, + RNym, + ipk, + Disclosure, + msg, + rhIndex, eidIndex, + cri, + rng, + curve, + tr, + t1, t2, t3, + APrime, ABar, BPrime, + nonRevokedProofHashData, + E, + Nonce, + rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, + rAttrs, + prover, + HiddenIndices, + opts.EidNym, + metadata, + ) +} + +func newSignatureWithEIDNymAndRHNym( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure []byte, + msg []byte, + rhIndex, eidIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + curve *math.Curve, + tr Translator, + metadata *opts.IdemixSignerMetadata, +) (*Signature, *opts.IdemixSignerMetadata, error) { + if Disclosure[eidIndex] != 0 { + return nil, nil, errors.Errorf("cannot create idemix signature: disclosure of enrollment ID requested for NewSignatureWithEIDNym") + } + + if Disclosure[rhIndex] != 0 { + return nil, nil, errors.Errorf("cannot create idemix signature: disclosure of revocation handle requested for NewSignatureWithEIDNymAndRHNym") + } + + t1, t2, t3, APrime, ABar, BPrime, nonRevokedProofHashData, E, Nonce, rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, rAttrs, prover, HiddenIndices, err := prepare(cred, sk, Nym, RNym, ipk, Disclosure, msg, rhIndex, cri, rng, curve, tr) + if err != nil { + return nil, nil, err + } + + return finalise( + cred, + sk, + Nym, + RNym, + ipk, + Disclosure, + msg, + rhIndex, eidIndex, + cri, + rng, + curve, + tr, + t1, t2, t3, + APrime, ABar, BPrime, + nonRevokedProofHashData, + E, + Nonce, + rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, + rAttrs, + prover, + HiddenIndices, + opts.EidNymRhNym, + metadata, + ) +} + +func prepare( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure []byte, + msg []byte, + rhIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + curve *math.Curve, + tr Translator, +) (*math.G1, *math.G1, *math.G1, *math.G1, *math.G1, *math.G1, + []byte, + *math.Zr, + *math.Zr, + *math.Zr, *math.Zr, *math.Zr, *math.Zr, *math.Zr, *math.Zr, *math.Zr, *math.Zr, *math.Zr, + []*math.Zr, + nonRevokedProver, + []int, error, +) { + // Validate inputs + if cred == nil || sk == nil || Nym == nil || RNym == nil || ipk == nil || rng == nil || cri == nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.Errorf("cannot create idemix signature: received nil input") + } + + if rhIndex < 0 || rhIndex >= len(ipk.AttributeNames) || len(Disclosure) != len(ipk.AttributeNames) { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.Errorf("cannot create idemix signature: received invalid input") + } + + if cri.RevocationAlg != int32(ALG_NO_REVOCATION) && Disclosure[rhIndex] == 1 { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.Errorf("Attribute %d is disclosed but also used as revocation handle attribute, which should remain hidden.", rhIndex) + } + + // locate the indices of the attributes to hide and sample randomness for them + HiddenIndices := hiddenIndices(Disclosure) + + // Generate required randomness r_1, r_2 + r1 := curve.NewRandomZr(rng) + r2 := curve.NewRandomZr(rng) + // Set r_3 as \frac{1}{r_1} + r3 := r1.Copy() + r3.InvModP(curve.GroupOrder) + + // Sample a nonce + Nonce := curve.NewRandomZr(rng) + + // Parse credential + A, err := tr.G1FromProto(cred.A) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + + B, err := tr.G1FromProto(cred.B) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + + // Randomize credential + + // Compute A' as A^{r_1} + APrime := A.Mul(r1) + // logger.Printf("Signature Generation : \n"+ + // " [APrime:%v]\n", + // APrime.Bytes(), + // ) + + // Compute ABar as A'^{-e} b^{r1} + ABar := B.Mul(r1) + ABar.Sub(APrime.Mul(curve.NewZrFromBytes(cred.E))) + // logger.Printf("Signature Generation : \n"+ + // " [ABar:%v]\n", + // ABar.Bytes(), + // ) + + // Compute B' as b^{r1} / h_r^{r2}, where h_r is h_r + BPrime := B.Mul(r1) + HRand, err := tr.G1FromProto(ipk.HRand) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + + // Parse h_{sk} from ipk + HSk, err := tr.G1FromProto(ipk.HSk) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + + BPrime.Sub(HRand.Mul(r2)) + + S := curve.NewZrFromBytes(cred.S) + E := curve.NewZrFromBytes(cred.E) + + // Compute s' as s - r_2 \cdot r_3 + sPrime := curve.ModSub(S, curve.ModMul(r2, r3, curve.GroupOrder), curve.GroupOrder) + + // The rest of this function constructs the non-interactive zero knowledge proof + // that links the signature, the non-disclosed attributes and the nym. + + // Sample the randomness used to compute the commitment values (aka t-values) for the ZKP + rSk := curve.NewRandomZr(rng) + re := curve.NewRandomZr(rng) + rR2 := curve.NewRandomZr(rng) + rR3 := curve.NewRandomZr(rng) + rSPrime := curve.NewRandomZr(rng) + rRNym := curve.NewRandomZr(rng) + + rAttrs := make([]*math.Zr, len(HiddenIndices)) + for i := range HiddenIndices { + rAttrs[i] = curve.NewRandomZr(rng) + } + + // First compute the non-revocation proof. + // The challenge of the ZKP needs to depend on it, as well. + prover, err := getNonRevocationProver(RevocationAlgorithm(cri.RevocationAlg)) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + nonRevokedProofHashData, err := prover.getFSContribution( + curve.NewZrFromBytes(cred.Attrs[rhIndex]), + rAttrs[sort.SearchInts(HiddenIndices, rhIndex)], + cri, + rng, + ) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, "failed to compute non-revoked proof") + } + + // Step 1: First message (t-values) + + HAttrs := make([]*math.G1, len(ipk.HAttrs)) + for i := range ipk.HAttrs { + var err error + HAttrs[i], err = tr.G1FromProto(ipk.HAttrs[i]) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + } + + // t1 is related to knowledge of the credential (recall, it is a BBS+ signature) + t1 := APrime.Mul2(re, HRand, rR2) // A'^{r_E} \cdot h_r^{r_{r2}} + + // t2: is related to knowledge of the non-disclosed attributes that signed in (A,B,S,E) + t2 := HRand.Mul(rSPrime) // h_r^{r_{s'}} + t2.Add(BPrime.Mul2(rR3, HSk, rSk)) // B'^{r_{r3}} \cdot h_{sk}^{r_{sk}} + for i := 0; i < len(HiddenIndices)/2; i++ { + t2.Add( + // \cdot h_{2 \cdot i}^{r_{attrs,i} + HAttrs[HiddenIndices[2*i]].Mul2( + rAttrs[2*i], + HAttrs[HiddenIndices[2*i+1]], + rAttrs[2*i+1], + ), + ) + } + if len(HiddenIndices)%2 != 0 { + t2.Add(HAttrs[HiddenIndices[len(HiddenIndices)-1]].Mul(rAttrs[len(HiddenIndices)-1])) + } + + // t3 is related to the knowledge of the secrets behind the pseudonym, which is also signed in (A,B,S,E) + t3 := HSk.Mul2(rSk, HRand, rRNym) // h_{sk}^{r_{sk}} \cdot h_r^{r_{rnym}} + + return t1, t2, t3, + APrime, ABar, BPrime, + nonRevokedProofHashData, + E, + Nonce, + rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym, + rAttrs, + prover, + HiddenIndices, nil +} + +func finalise( + cred *Credential, + sk *math.Zr, + Nym *math.G1, + RNym *math.Zr, + ipk *IssuerPublicKey, + Disclosure []byte, + msg []byte, + rhIndex, eidIndex int, + cri *CredentialRevocationInformation, + rng io.Reader, + curve *math.Curve, + tr Translator, + t1, t2, t3 *math.G1, + APrime, ABar, BPrime *math.G1, + nonRevokedProofHashData []byte, + E *math.Zr, + Nonce *math.Zr, + rSk, rSPrime, rR2, rR3, r2, r3, re, sPrime, rRNym *math.Zr, + rAttrs []*math.Zr, + prover nonRevokedProver, + HiddenIndices []int, + sigType opts.SignatureType, + metadata *opts.IdemixSignerMetadata, +) (*Signature, *opts.IdemixSignerMetadata, error) { + + var Nym_eid, Nym_rh *math.G1 + var t4_eid, t4_rh *math.G1 + var r_r_eid, r_eid, r_r_rh, r_rh *math.Zr + var EID, RH *math.Zr + var err error + + if sigType == opts.EidNym || sigType == opts.EidNymRhNym { + EID = curve.NewZrFromBytes(cred.Attrs[eidIndex]) + if metadata != nil { + if metadata.EidNymAuditData == nil { + return nil, nil, errors.Errorf("invalid argument, expected metadata") + } + + if !metadata.EidNymAuditData.Attr.Equals(EID) { + return nil, nil, errors.Errorf("invalid argument, eid nym audit metadata does not match (1)") + } + r_eid = metadata.EidNymAuditData.Rand + } else { + r_eid = curve.NewRandomZr(rng) + } + + r_a_eid := rAttrs[sort.SearchInts(HiddenIndices, eidIndex)] + H_a_eid, err := tr.G1FromProto(ipk.HAttrs[eidIndex]) + if err != nil { + return nil, nil, err + } + + a_eid := EID + HRand, err := tr.G1FromProto(ipk.HRand) + if err != nil { + return nil, nil, err + } + + // Generate new required randomness r_r_eid + r_r_eid = curve.NewRandomZr(rng) + + // Nym_eid is a hiding and binding commitment to the enrollment id + Nym_eid = H_a_eid.Mul2(a_eid, HRand, r_eid) // H_{a_{eid}}^{a_{eid}} \cdot H_{r}^{r_{eid}} + + if metadata != nil { + if !metadata.EidNymAuditData.Nym.Equals(Nym_eid) { + return nil, nil, errors.Errorf("invalid argument, eid nym audit metadata does not match (2)") + } + } + + // t4 is the new t-value for the eid nym computed above + t4_eid = H_a_eid.Mul2(r_a_eid, HRand, r_r_eid) // H_{a_{eid}}^{r_{a_{2}}} \cdot H_{r}^{r_{r_{eid}}} + } + + if sigType == opts.EidNymRhNym { + RH = curve.NewZrFromBytes(cred.Attrs[rhIndex]) + if metadata != nil { + if metadata.RhNymAuditData == nil { + return nil, nil, errors.Errorf("invalid argument, expected metadata") + } + + if !metadata.RhNymAuditData.Attr.Equals(RH) { + return nil, nil, errors.Errorf("invalid argument, rh nym audit metadata does not match (1)") + } + r_rh = metadata.RhNymAuditData.Rand + } else { + r_rh = curve.NewRandomZr(rng) + } + + r_a_rh := rAttrs[sort.SearchInts(HiddenIndices, rhIndex)] + H_a_rh, err := tr.G1FromProto(ipk.HAttrs[rhIndex]) + if err != nil { + return nil, nil, err + } + + a_rh := RH + HRand, err := tr.G1FromProto(ipk.HRand) + if err != nil { + return nil, nil, err + } + + // Generate new required randomness r_r_rh + r_r_rh = curve.NewRandomZr(rng) + + // Nym_rh is a hiding and binding commitment to the revocation handle + Nym_rh = H_a_rh.Mul2(a_rh, HRand, r_rh) // H_{a_{rh}}^{a_{rh}} \cdot H_{r}^{r_{rh}} + + if metadata != nil { + if !metadata.RhNymAuditData.Nym.Equals(Nym_rh) { + return nil, nil, errors.Errorf("invalid argument, rh nym audit metadata does not match (2)") + } + } + + // t4 is the new t-value for the rh nym computed above + t4_rh = H_a_rh.Mul2(r_a_rh, HRand, r_r_rh) // H_{a_{rh}}^{r_{a_{2}}} \cdot H_{r}^{r_{r_{rh}}} + } + + // Step 2: Compute the Fiat-Shamir hash, forming the challenge of the ZKP. + + // Compute the Fiat-Shamir hash, forming the challenge of the ZKP. + // proofData is the data being hashed, it consists of: + // the signature label + // 7 elements of G1 each taking 2*math.FieldBytes+1 bytes + // one bigint (hash of the issuer public key) of length math.FieldBytes + // disclosed attributes + // message being signed + // the minimum amount of bytes needed for the nonrevocation proof + pdl := curve.ScalarByteSize + len(Disclosure) + len(msg) + ProofBytes[RevocationAlgorithm(cri.RevocationAlg)] + switch sigType { + case opts.Standard: // additional bytes for a standard sign label + pdl += len([]byte(signLabel)) + 7*curve.G1ByteSize + case opts.EidNym: // additional bytes for a sign label including an enrollment id attribute + pdl += len([]byte(signWithEidNymLabel)) + 9*curve.G1ByteSize + case opts.EidNymRhNym: // additional bytes for a sign label including both an enrollment id and a revocation handle attribute + pdl += len([]byte(signWithEidNymRhNymLabel)) + 11*curve.G1ByteSize + default: + panic("programming error") + } + proofData := make([]byte, pdl) + index := 0 + switch sigType { + case opts.Standard: + index = appendBytesString(proofData, index, signLabel) + case opts.EidNym: + index = appendBytesString(proofData, index, signWithEidNymLabel) + case opts.EidNymRhNym: + index = appendBytesString(proofData, index, signWithEidNymRhNymLabel) + default: + panic("programming error") + } + index = appendBytesG1(proofData, index, t1) + index = appendBytesG1(proofData, index, t2) + index = appendBytesG1(proofData, index, t3) + index = appendBytesG1(proofData, index, APrime) + index = appendBytesG1(proofData, index, ABar) + index = appendBytesG1(proofData, index, BPrime) + index = appendBytesG1(proofData, index, Nym) + if sigType == opts.EidNym || sigType == opts.EidNymRhNym { + index = appendBytesG1(proofData, index, Nym_eid) + index = appendBytesG1(proofData, index, t4_eid) + } + if sigType == opts.EidNymRhNym { + index = appendBytesG1(proofData, index, Nym_rh) + index = appendBytesG1(proofData, index, t4_rh) + } + index = appendBytes(proofData, index, nonRevokedProofHashData) + copy(proofData[index:], ipk.Hash) + index = index + curve.ScalarByteSize + copy(proofData[index:], Disclosure) + index = index + len(Disclosure) + copy(proofData[index:], msg) + c := curve.HashToZr(proofData) + + // add the previous hash and the nonce and hash again to compute a second hash (C value) + index = 0 + proofData = proofData[:2*curve.ScalarByteSize] + index = appendBytesBig(proofData, index, c) + appendBytesBig(proofData, index, Nonce) + ProofC := curve.HashToZr(proofData) + + // Step 3: reply to the challenge message (s-values) + ProofSSk := curve.ModAdd(rSk, curve.ModMul(ProofC, sk, curve.GroupOrder), curve.GroupOrder) // s_sk = rSK + C \cdot sk + ProofSE := curve.ModSub(re, curve.ModMul(ProofC, E, curve.GroupOrder), curve.GroupOrder) // s_e = re + C \cdot E + ProofSR2 := curve.ModAdd(rR2, curve.ModMul(ProofC, r2, curve.GroupOrder), curve.GroupOrder) // s_r2 = rR2 + C \cdot r2 + ProofSR3 := curve.ModSub(rR3, curve.ModMul(ProofC, r3, curve.GroupOrder), curve.GroupOrder) // s_r3 = rR3 + C \cdot r3 + ProofSSPrime := curve.ModAdd(rSPrime, curve.ModMul(ProofC, sPrime, curve.GroupOrder), curve.GroupOrder) // s_S' = rSPrime + C \cdot sPrime + ProofSRNym := curve.ModAdd(rRNym, curve.ModMul(ProofC, RNym, curve.GroupOrder), curve.GroupOrder) // s_RNym = rRNym + C \cdot RNym + ProofSAttrs := make([][]byte, len(HiddenIndices)) + for i, j := range HiddenIndices { + ProofSAttrs[i] = + // s_attrsi = rAttrsi + C \cdot cred.Attrs[j] + curve.ModAdd(rAttrs[i], curve.ModMul(ProofC, curve.NewZrFromBytes(cred.Attrs[j]), curve.GroupOrder), curve.GroupOrder).Bytes() + } + + // Compute the revocation part + nonRevokedProof, err := prover.getNonRevokedProof(ProofC) + if err != nil { + return nil, nil, err + } + + // logger.Printf("Signature Generation : \n"+ + // " [t1:%v]\n,"+ + // " [t2:%v]\n,"+ + // " [t3:%v]\n,"+ + // " [APrime:%v]\n,"+ + // " [ABar:%v]\n,"+ + // " [BPrime:%v]\n,"+ + // " [Nym:%v]\n,"+ + // " [nonRevokedProofBytes:%v]\n,"+ + // " [ipk.Hash:%v]\n,"+ + // " [Disclosure:%v]\n,"+ + // " [msg:%v]\n,"+ + // " [ProofData:%v]\n,"+ + // " [ProofC:%v]\n"+ + // " [HSk:%v]\n,"+ + // " [ProofSSK:%v]\n,"+ + // " [HRand:%v]\n,"+ + // " [ProofSRNym:%v]\n", + // t1.Bytes(), + // t2.Bytes(), + // t3.Bytes(), + // APrime.Bytes(), + // ABar.Bytes(), + // BPrime.Bytes(), + // Nym.Bytes(), + // nil, + // ipk.Hash, + // Disclosure, + // msg, + // proofData, + // ProofC.Bytes(), + // HSk.Bytes(), + // ProofSSk.Bytes(), + // HRand.Bytes(), + // ProofSRNym.Bytes(), + // ) + + // We are done. Return signature + sig := &Signature{ + APrime: tr.G1ToProto(APrime), + ABar: tr.G1ToProto(ABar), + BPrime: tr.G1ToProto(BPrime), + ProofC: ProofC.Bytes(), + ProofSSk: ProofSSk.Bytes(), + ProofSE: ProofSE.Bytes(), + ProofSR2: ProofSR2.Bytes(), + ProofSR3: ProofSR3.Bytes(), + ProofSSPrime: ProofSSPrime.Bytes(), + ProofSAttrs: ProofSAttrs, + Nonce: Nonce.Bytes(), + Nym: tr.G1ToProto(Nym), + ProofSRNym: ProofSRNym.Bytes(), + RevocationEpochPk: cri.EpochPk, + RevocationPkSig: cri.EpochPkSig, + Epoch: cri.Epoch, + NonRevocationProof: nonRevokedProof, + } + + if sigType == opts.EidNym || sigType == opts.EidNymRhNym { + ProofSEid := curve.ModAdd(r_r_eid, curve.ModMul(ProofC, r_eid, curve.GroupOrder), curve.GroupOrder) // s_{r{eid}} = r_r_eid + C \cdot r_eid + sig.EidNym = &EIDNym{ + Nym: tr.G1ToProto(Nym_eid), + ProofSEid: ProofSEid.Bytes(), + } + } + + if sigType == opts.EidNymRhNym { + ProofSRh := curve.ModAdd(r_r_rh, curve.ModMul(ProofC, r_rh, curve.GroupOrder), curve.GroupOrder) + sig.RhNym = &RHNym{ + Nym: tr.G1ToProto(Nym_rh), + ProofSRh: ProofSRh.Bytes(), + } + } + + var m *opts.IdemixSignerMetadata + if sigType == opts.EidNym { + m = &opts.IdemixSignerMetadata{ + EidNymAuditData: &opts.AttrNymAuditData{ + Nym: Nym_eid, + Rand: r_eid, + Attr: EID, + }, + } + } + + if sigType == opts.EidNymRhNym { + m = &opts.IdemixSignerMetadata{ + EidNymAuditData: &opts.AttrNymAuditData{ + Nym: Nym_eid, + Rand: r_eid, + Attr: EID, + }, + RhNymAuditData: &opts.AttrNymAuditData{ + Nym: Nym_rh, + Rand: r_rh, + Attr: RH, + }, + } + } + + return sig, m, nil +} + +func (sig *Signature) AuditNymEid( + ipk *IssuerPublicKey, + eidAttr *math.Zr, + eidIndex int, + RNymEid *math.Zr, + curve *math.Curve, + t Translator, +) error { + // Validate inputs + if ipk == nil { + return errors.Errorf("cannot verify idemix signature: received nil input") + } + + if sig.EidNym == nil || sig.EidNym.Nym == nil { + return errors.Errorf("no EidNym provided") + } + + if len(ipk.HAttrs) <= eidIndex { + return errors.Errorf("could not access H_a_eid in array") + } + + H_a_eid, err := t.G1FromProto(ipk.HAttrs[eidIndex]) + if err != nil { + return errors.Wrap(err, "could not deserialize H_a_eid") + } + + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return errors.Wrap(err, "could not deserialize HRand") + } + + EidNym, err := t.G1FromProto(sig.EidNym.Nym) + if err != nil { + return errors.Wrap(err, "could not deserialize EidNym") + } + + Nym_eid := H_a_eid.Mul2(eidAttr, HRand, RNymEid) + + if !Nym_eid.Equals(EidNym) { + return errors.New("eid nym does not match") + } + + return nil +} + +func (sig *Signature) AuditNymRh( + ipk *IssuerPublicKey, + rhAttr *math.Zr, + rhIndex int, + RNymRh *math.Zr, + curve *math.Curve, + t Translator, +) error { + // Validate inputs + if ipk == nil { + return errors.Errorf("cannot verify idemix signature: received nil input") + } + + if sig.RhNym == nil || sig.RhNym.Nym == nil { + return errors.Errorf("no RhNym provided") + } + + if len(ipk.HAttrs) <= rhIndex { + return errors.Errorf("could not access H_a_rh in array") + } + + H_a_rh, err := t.G1FromProto(ipk.HAttrs[rhIndex]) + if err != nil { + return errors.Wrap(err, "could not deserialize H_a_rh") + } + + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return errors.Wrap(err, "could not deserialize HRand") + } + + RhNym, err := t.G1FromProto(sig.RhNym.Nym) + if err != nil { + return errors.Wrap(err, "could not deserialize RhNym") + } + + Nym_rh := H_a_rh.Mul2(rhAttr, HRand, RNymRh) + + if !Nym_rh.Equals(RhNym) { + return errors.New("rh nym does not match") + } + + return nil +} + +// Ver verifies an idemix signature +// Disclosure steers which attributes it expects to be disclosed +// attributeValues contains the desired attribute values. +// This function will check that if attribute i is disclosed, the i-th attribute equals attributeValues[i]. +func (sig *Signature) Ver( + Disclosure []byte, + ipk *IssuerPublicKey, + msg []byte, + attributeValues []*math.Zr, + rhIndex, eidIndex int, + revPk *ecdsa.PublicKey, + epoch int, + curve *math.Curve, + t Translator, + verType opts.VerificationType, + meta *opts.IdemixSignerMetadata, +) error { + // Validate inputs + if ipk == nil { + return errors.Errorf("cannot verify idemix signature: received nil input") + } + + if rhIndex < 0 || rhIndex >= len(ipk.AttributeNames) || len(Disclosure) != len(ipk.AttributeNames) { + return errors.Errorf("cannot verify idemix signature: received invalid input") + } + + if sig.NonRevocationProof.RevocationAlg != int32(ALG_NO_REVOCATION) && Disclosure[rhIndex] == 1 { + return errors.Errorf("Attribute %d is disclosed but is also used as revocation handle, which should remain hidden.", rhIndex) + } + if verType == opts.ExpectEidNym && + (sig.EidNym == nil || sig.EidNym.Nym == nil || sig.EidNym.ProofSEid == nil) { + return errors.Errorf("no EidNym provided but ExpectEidNym required") + } + + if verType == opts.ExpectEidNymRhNym { + if sig.EidNym == nil || sig.EidNym.Nym == nil || sig.EidNym.ProofSEid == nil { + return errors.Errorf("no EidNym provided but ExpectEidNymRhNym required") + } + if sig.RhNym == nil || sig.RhNym.Nym == nil || sig.RhNym.ProofSRh == nil { + return errors.Errorf("no RhNym provided but ExpectEidNymRhNym required") + } + } + + if verType == opts.ExpectStandard { + if sig.RhNym != nil { + return errors.Errorf("RhNym available but ExpectStandard required") + } + if sig.EidNym != nil { + return errors.Errorf("EidNym available but ExpectStandard required") + } + } + + verifyRHNym := (verType == opts.BestEffort && sig.RhNym != nil) || verType == opts.ExpectEidNymRhNym + verifyEIDNym := (verType == opts.BestEffort && sig.EidNym != nil) || verType == opts.ExpectEidNym || verType == opts.ExpectEidNymRhNym || verifyRHNym + + HiddenIndices := hiddenIndices(Disclosure) + + // Parse signature + APrime, err := t.G1FromProto(sig.GetAPrime()) + if err != nil { + return err + } + //logger.Printf("Signature Verification : \n"+ + // " [APrime:%v]\n", + // APrime.Bytes(), + //) + ABar, err := t.G1FromProto(sig.GetABar()) + if err != nil { + return err + } + //logger.Printf("Signature Verification : \n"+ + // " [ABar:%v]\n", + // ABar.Bytes(), + //) + BPrime, err := t.G1FromProto(sig.GetBPrime()) + if err != nil { + return err + } + Nym, err := t.G1FromProto(sig.GetNym()) + if err != nil { + return err + } + ProofC := curve.NewZrFromBytes(sig.GetProofC()) + ProofSSk := curve.NewZrFromBytes(sig.GetProofSSk()) + ProofSE := curve.NewZrFromBytes(sig.GetProofSE()) + ProofSR2 := curve.NewZrFromBytes(sig.GetProofSR2()) + ProofSR3 := curve.NewZrFromBytes(sig.GetProofSR3()) + ProofSSPrime := curve.NewZrFromBytes(sig.GetProofSSPrime()) + ProofSRNym := curve.NewZrFromBytes(sig.GetProofSRNym()) + ProofSAttrs := make([]*math.Zr, len(sig.GetProofSAttrs())) + if len(sig.ProofSAttrs) != len(HiddenIndices) { + return errors.Errorf("signature invalid: incorrect amount of s-values for AttributeProofSpec") + } + for i, b := range sig.ProofSAttrs { + ProofSAttrs[i] = curve.NewZrFromBytes(b) + } + Nonce := curve.NewZrFromBytes(sig.GetNonce()) + + // Parse issuer public key + W, err := t.G2FromProto(ipk.W) + if err != nil { + return err + } + HRand, err := t.G1FromProto(ipk.HRand) + if err != nil { + return err + } + HSk, err := t.G1FromProto(ipk.HSk) + if err != nil { + return err + } + //logger.Printf("Signature Verification : \n"+ + // " [W:%v]\n", + // W.Bytes(), + //) + + // Verify signature + if APrime.IsInfinity() { + return errors.Errorf("signature invalid: APrime = 1") + } + temp1 := curve.Pairing(W, APrime) + temp2 := curve.Pairing(curve.GenG2, ABar) + temp2.Inverse() + temp1.Mul(temp2) + if !curve.FExp(temp1).IsUnity() { + return errors.Errorf("signature invalid: APrime and ABar don't have the expected structure") + } + + // Verify ZK proof + + // Recover t-values + + HAttrs := make([]*math.G1, len(ipk.HAttrs)) + for i := range ipk.HAttrs { + var err error + HAttrs[i], err = t.G1FromProto(ipk.HAttrs[i]) + if err != nil { + return err + } + } + // Recompute t1 + t1 := APrime.Mul2(ProofSE, HRand, ProofSR2) + temp := curve.NewG1() + temp.Clone(ABar) + temp.Sub(BPrime) + t1.Sub(temp.Mul(ProofC)) + + // Recompute t2 + t2 := HRand.Mul(ProofSSPrime) + t2.Add(BPrime.Mul2(ProofSR3, HSk, ProofSSk)) + for i := 0; i < len(HiddenIndices)/2; i++ { + t2.Add(HAttrs[HiddenIndices[2*i]].Mul2(ProofSAttrs[2*i], HAttrs[HiddenIndices[2*i+1]], ProofSAttrs[2*i+1])) + } + if len(HiddenIndices)%2 != 0 { + t2.Add(HAttrs[HiddenIndices[len(HiddenIndices)-1]].Mul(ProofSAttrs[len(HiddenIndices)-1])) + } + temp = curve.NewG1() + temp.Clone(curve.GenG1) + for index, disclose := range Disclosure { + if disclose != 0 { + temp.Add(HAttrs[index].Mul(attributeValues[index])) + } + } + t2.Add(temp.Mul(ProofC)) + + // Recompute t3 + t3 := HSk.Mul2(ProofSSk, HRand, ProofSRNym) + t3.Sub(Nym.Mul(ProofC)) + + // Attribute pseudonym extension signature verification + var t4_eid *math.G1 + if verifyEIDNym { + H_a_eid, err := t.G1FromProto(ipk.HAttrs[eidIndex]) + if err != nil { + return err + } + + t4_eid = H_a_eid.Mul2(ProofSAttrs[sort.SearchInts(HiddenIndices, eidIndex)], HRand, curve.NewZrFromBytes(sig.EidNym.ProofSEid)) + EidNym, err := t.G1FromProto(sig.EidNym.Nym) + if err != nil { + return err + } + t4_eid.Sub(EidNym.Mul(ProofC)) + } + var t4_rh *math.G1 + if verifyRHNym { + H_a_rh, err := t.G1FromProto(ipk.HAttrs[rhIndex]) + if err != nil { + return err + } + + t4_rh = H_a_rh.Mul2(ProofSAttrs[sort.SearchInts(HiddenIndices, rhIndex)], HRand, curve.NewZrFromBytes(sig.RhNym.ProofSRh)) + RhNym, err := t.G1FromProto(sig.RhNym.Nym) + if err != nil { + return err + } + t4_rh.Sub(RhNym.Mul(ProofC)) + } + // add contribution from the non-revocation proof + nonRevokedVer, err := getNonRevocationVerifier(RevocationAlgorithm(sig.NonRevocationProof.RevocationAlg)) + if err != nil { + return err + } + + i := sort.SearchInts(HiddenIndices, rhIndex) + proofSRh := ProofSAttrs[i] + RevocationEpochPk, err := t.G2FromProto(sig.RevocationEpochPk) + if err != nil { + return err + } + + nonRevokedProofBytes, err := nonRevokedVer.recomputeFSContribution(sig.NonRevocationProof, ProofC, RevocationEpochPk, proofSRh) + if err != nil { + return err + } + // Recompute challenge + // proofData is the data being hashed, it consists of: + // the signature label + // 7 elements of G1 each taking 2*math.FieldBytes+1 bytes + // one bigint (hash of the issuer public key) of length math.FieldBytes + // disclosed attributes + // message that was signed + // pdl is minimum length of proof data + pdl := curve.ScalarByteSize + len(Disclosure) + len(msg) + ProofBytes[RevocationAlgorithm(sig.NonRevocationProof.RevocationAlg)] + if verifyRHNym { // additional length for both an enrollment id and revocation handle attribute + pdl += len([]byte(signWithEidNymRhNymLabel)) + 11*curve.G1ByteSize + } else if verifyEIDNym { // additional length for an enrollment id attribute + pdl += len([]byte(signWithEidNymLabel)) + 9*curve.G1ByteSize + } else { // additional length for a standard sign label + pdl += len([]byte(signLabel)) + 7*curve.G1ByteSize + } + proofData := make([]byte, pdl) + index := 0 + if verifyRHNym { + index = appendBytesString(proofData, index, signWithEidNymRhNymLabel) + } else if verifyEIDNym { + index = appendBytesString(proofData, index, signWithEidNymLabel) + } else { + index = appendBytesString(proofData, index, signLabel) + } + index = appendBytesG1(proofData, index, t1) + index = appendBytesG1(proofData, index, t2) + index = appendBytesG1(proofData, index, t3) + index = appendBytesG1(proofData, index, APrime) + index = appendBytesG1(proofData, index, ABar) + index = appendBytesG1(proofData, index, BPrime) + index = appendBytesG1(proofData, index, Nym) + if verifyEIDNym { + EidNym, err := t.G1FromProto(sig.EidNym.Nym) + if err != nil { + return err + } + index = appendBytesG1(proofData, index, EidNym) + index = appendBytesG1(proofData, index, t4_eid) + } + if verifyRHNym { + RhNym, err := t.G1FromProto(sig.RhNym.Nym) + if err != nil { + return err + } + index = appendBytesG1(proofData, index, RhNym) + index = appendBytesG1(proofData, index, t4_rh) + } + index = appendBytes(proofData, index, nonRevokedProofBytes) + copy(proofData[index:], ipk.Hash) + index = index + curve.ScalarByteSize + copy(proofData[index:], Disclosure) + index = index + len(Disclosure) + copy(proofData[index:], msg) + + c := curve.HashToZr(proofData) + index = 0 + proofData = proofData[:2*curve.ScalarByteSize] + index = appendBytesBig(proofData, index, c) + appendBytesBig(proofData, index, Nonce) + + // audit eid nym if data provided and verification requested + if (verifyEIDNym || verifyRHNym) && meta != nil { + EidNym, err := t.G1FromProto(sig.EidNym.Nym) + if err != nil { + return err + } + + if meta.EidNymAuditData != nil { + H_a_eid, err := t.G1FromProto(ipk.HAttrs[eidIndex]) + if err != nil { + return err + } + + Nym_eid := H_a_eid.Mul2(meta.EidNymAuditData.Attr, HRand, meta.EidNymAuditData.Rand) + if !Nym_eid.Equals(EidNym) { + return errors.Errorf("signature invalid: nym eid validation failed, does not match regenerated nym eid") + } + + if meta.EidNymAuditData.Nym != nil && !EidNym.Equals(meta.EidNymAuditData.Nym) { + return errors.Errorf("signature invalid: nym eid validation failed, does not match metadata") + } + } + + if len(meta.EidNym) != 0 { + NymEID, err := curve.NewG1FromBytes(meta.EidNym) + if err != nil { + return errors.Errorf("signature invalid: nym eid validation failed, failed to unmarshal meta nym eid") + } + if !NymEID.Equals(EidNym) { + return errors.Errorf("signature invalid: nym eid validation failed, signature nym eid does not match metadata") + } + } + } + // audit rh nym if data provided and verification requested + if verifyRHNym && meta != nil { + RhNym, err := t.G1FromProto(sig.RhNym.Nym) + if err != nil { + return err + } + + if meta.RhNymAuditData != nil { + H_a_rh, err := t.G1FromProto(ipk.HAttrs[rhIndex]) + if err != nil { + return err + } + + Nym_rh := H_a_rh.Mul2(meta.RhNymAuditData.Attr, HRand, meta.RhNymAuditData.Rand) + if !Nym_rh.Equals(RhNym) { + return errors.Errorf("signature invalid: nym rh validation failed, does not match regenerated nym rh") + } + + if meta.RhNymAuditData.Nym != nil && !RhNym.Equals(meta.RhNymAuditData.Nym) { + return errors.Errorf("signature invalid: nym rh validation failed, does not match metadata") + } + } + + if len(meta.RhNym) != 0 { + NymRH, err := curve.NewG1FromBytes(meta.RhNym) + if err != nil { + return errors.Errorf("signature invalid: nym rh validation failed, failed to unmarshal meta nym rh") + } + if !NymRH.Equals(RhNym) { + return errors.Errorf("signature invalid: nym rh validation failed, signature nym rh does not match metadata") + } + } + } + + recomputedProofC := curve.HashToZr(proofData) + if !ProofC.Equals(recomputedProofC) { + // This debug line helps identify where the mismatch happened + logger.Printf("Signature Verification : \n"+ + " [t1:%v]\n,"+ + " [t2:%v]\n,"+ + " [t3:%v]\n,"+ + " [APrime:%v]\n,"+ + " [ABar:%v]\n,"+ + " [BPrime:%v]\n,"+ + " [Nym:%v]\n,"+ + " [nonRevokedProofBytes:%v]\n,"+ + " [ipk.Hash:%v]\n,"+ + " [Disclosure:%v]\n,"+ + " [msg:%v]\n,"+ + " [proofdata:%v]\n,"+ + " [ProofC:%v]\n,"+ + " [recomputedProofC:%v]\n,"+ + " [HSk:%v]\n,"+ + " [ProofSSK:%v]\n,"+ + " [HRand:%v]\n,"+ + " [ProofSRNym:%v]\n", + t1.Bytes(), + t2.Bytes(), + t3.Bytes(), + APrime.Bytes(), + ABar.Bytes(), + BPrime.Bytes(), + Nym.Bytes(), + nonRevokedProofBytes, + ipk.Hash, + Disclosure, + msg, + proofData, + ProofC.Bytes(), + recomputedProofC.Bytes(), + HSk.Bytes(), + ProofSSk.Bytes(), + HRand.Bytes(), + ProofSRNym.Bytes(), + ) + return errors.Errorf("signature invalid: zero-knowledge proof is invalid") + } + + // Signature is valid + return nil +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.pb.go b/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.pb.go new file mode 100644 index 0000000..b85001f --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.pb.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto + +package amcl + +import ( + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// ECP is an elliptic curve point specified by its coordinates +// ECP corresponds to an element of the first group (G1) +type ECP struct { + X []byte `protobuf:"bytes,1,opt,name=x,proto3" json:"x,omitempty"` + Y []byte `protobuf:"bytes,2,opt,name=y,proto3" json:"y,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ECP) Reset() { *m = ECP{} } +func (m *ECP) String() string { return proto.CompactTextString(m) } +func (*ECP) ProtoMessage() {} +func (*ECP) Descriptor() ([]byte, []int) { + return fileDescriptor_250ddfa5c5f8dbbb, []int{0} +} + +func (m *ECP) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ECP.Unmarshal(m, b) +} +func (m *ECP) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ECP.Marshal(b, m, deterministic) +} +func (m *ECP) XXX_Merge(src proto.Message) { + xxx_messageInfo_ECP.Merge(m, src) +} +func (m *ECP) XXX_Size() int { + return xxx_messageInfo_ECP.Size(m) +} +func (m *ECP) XXX_DiscardUnknown() { + xxx_messageInfo_ECP.DiscardUnknown(m) +} + +var xxx_messageInfo_ECP proto.InternalMessageInfo + +func (m *ECP) GetX() []byte { + if m != nil { + return m.X + } + return nil +} + +func (m *ECP) GetY() []byte { + if m != nil { + return m.Y + } + return nil +} + +// ECP2 is an elliptic curve point specified by its coordinates +// ECP2 corresponds to an element of the second group (G2) +type ECP2 struct { + Xa []byte `protobuf:"bytes,1,opt,name=xa,proto3" json:"xa,omitempty"` + Xb []byte `protobuf:"bytes,2,opt,name=xb,proto3" json:"xb,omitempty"` + Ya []byte `protobuf:"bytes,3,opt,name=ya,proto3" json:"ya,omitempty"` + Yb []byte `protobuf:"bytes,4,opt,name=yb,proto3" json:"yb,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ECP2) Reset() { *m = ECP2{} } +func (m *ECP2) String() string { return proto.CompactTextString(m) } +func (*ECP2) ProtoMessage() {} +func (*ECP2) Descriptor() ([]byte, []int) { + return fileDescriptor_250ddfa5c5f8dbbb, []int{1} +} + +func (m *ECP2) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ECP2.Unmarshal(m, b) +} +func (m *ECP2) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ECP2.Marshal(b, m, deterministic) +} +func (m *ECP2) XXX_Merge(src proto.Message) { + xxx_messageInfo_ECP2.Merge(m, src) +} +func (m *ECP2) XXX_Size() int { + return xxx_messageInfo_ECP2.Size(m) +} +func (m *ECP2) XXX_DiscardUnknown() { + xxx_messageInfo_ECP2.DiscardUnknown(m) +} + +var xxx_messageInfo_ECP2 proto.InternalMessageInfo + +func (m *ECP2) GetXa() []byte { + if m != nil { + return m.Xa + } + return nil +} + +func (m *ECP2) GetXb() []byte { + if m != nil { + return m.Xb + } + return nil +} + +func (m *ECP2) GetYa() []byte { + if m != nil { + return m.Ya + } + return nil +} + +func (m *ECP2) GetYb() []byte { + if m != nil { + return m.Yb + } + return nil +} + +func init() { + proto.RegisterType((*ECP)(nil), "amcl.ECP") + proto.RegisterType((*ECP2)(nil), "amcl.ECP2") +} + +func init() { + proto.RegisterFile("bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto", fileDescriptor_250ddfa5c5f8dbbb) +} + +var fileDescriptor_250ddfa5c5f8dbbb = []byte{ + // 239 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x49, 0x4a, 0x4e, 0x2e, + 0x2e, 0xd0, 0x2f, 0x4e, 0xce, 0x48, 0xcd, 0x4d, 0x2d, 0xd6, 0x4f, 0xc9, 0xc9, 0x4f, 0xd7, 0x4f, + 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x29, 0x4a, 0xcc, 0x2b, 0xce, 0x49, 0x2c, 0xc9, 0x2f, + 0xd2, 0x4f, 0xcc, 0x4d, 0xce, 0x01, 0x13, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x2c, 0x20, + 0xb6, 0x92, 0x22, 0x17, 0xb3, 0xab, 0x73, 0x80, 0x10, 0x0f, 0x17, 0x63, 0x85, 0x04, 0xa3, 0x02, + 0xa3, 0x06, 0x4f, 0x10, 0x63, 0x05, 0x88, 0x57, 0x29, 0xc1, 0x04, 0xe1, 0x55, 0x2a, 0xb9, 0x71, + 0xb1, 0xb8, 0x3a, 0x07, 0x18, 0x09, 0xf1, 0x71, 0x31, 0x55, 0x24, 0x42, 0x15, 0x31, 0x55, 0x24, + 0x82, 0xf9, 0x49, 0x50, 0x65, 0x4c, 0x15, 0x49, 0x20, 0x7e, 0x65, 0xa2, 0x04, 0x33, 0x84, 0x5f, + 0x09, 0x96, 0xaf, 0x4c, 0x92, 0x60, 0x81, 0xf2, 0x93, 0x9c, 0xda, 0x18, 0xb9, 0x38, 0x92, 0xf3, + 0x73, 0xf5, 0x40, 0xf6, 0x3a, 0x71, 0x3a, 0xe6, 0x26, 0xe7, 0x04, 0x80, 0x1c, 0x12, 0xc0, 0x18, + 0x65, 0x9f, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0xef, 0xe9, 0xe4, 0xab, + 0x9f, 0x99, 0x92, 0x9a, 0x9b, 0x59, 0xa1, 0x4f, 0xb4, 0xb7, 0x16, 0x31, 0x31, 0x3b, 0x46, 0x44, + 0xac, 0x62, 0x62, 0x01, 0x19, 0x7a, 0x0a, 0x42, 0x3d, 0x62, 0x12, 0x00, 0x51, 0x31, 0xee, 0x01, + 0x4e, 0xbe, 0xa9, 0x25, 0x89, 0x29, 0x89, 0x25, 0x89, 0xaf, 0x20, 0x32, 0x49, 0x6c, 0xe0, 0x00, + 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x42, 0x17, 0x88, 0x52, 0x38, 0x01, 0x00, 0x00, +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto b/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto new file mode 100644 index 0000000..6ecfef3 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/amcl.proto @@ -0,0 +1,32 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +syntax = "proto3"; + +package amcl; + +option go_package = "github.com/IBM/idemix/bccsp/schemes/dlog/crypto/translator/amcl"; + +// The Identity Mixer protocols make use of pairings (bilinear maps) - +// functions that can be described as e: G1 x G2 -> GT that +// map group elements from the source groups (G1 and G2) to the target group +// Such groups can be represented by the points on an elliptic curve + +// ECP is an elliptic curve point specified by its coordinates +// ECP corresponds to an element of the first group (G1) +message ECP { + bytes x = 1; + bytes y = 2; +} + +// ECP2 is an elliptic curve point specified by its coordinates +// ECP2 corresponds to an element of the second group (G2) +message ECP2 { + bytes xa = 1; + bytes xb = 2; + bytes ya = 3; + bytes yb = 4; +} \ No newline at end of file diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn.go b/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn.go new file mode 100644 index 0000000..6d0320b --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn.go @@ -0,0 +1,168 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package amcl + +import ( + fmt "fmt" + + math "github.com/IBM/mathlib" +) + +type Fp256bn struct { + C *math.Curve +} + +func (a *Fp256bn) G1ToProto(g1 *math.G1) *ECP { + if g1 == nil { + panic("nil argument") + } + + bytes := g1.Bytes()[1:] + l := len(bytes) / 2 + return &ECP{ + X: bytes[:l], + Y: bytes[l:], + } +} + +func (a *Fp256bn) G1FromRawBytes(raw []byte) (*math.G1, error) { + l := len(raw) / 2 + + return a.G1FromProto(&ECP{ + X: raw[:l], + Y: raw[l:], + }) +} + +func (a *Fp256bn) G1FromProto(e *ECP) (*math.G1, error) { + if e == nil { + return nil, fmt.Errorf("nil argument") + } + + if len(e.X) != a.C.CoordByteSize || len(e.Y) != a.C.CoordByteSize { + return nil, fmt.Errorf("invalid marshalled length") + } + + bytes := make([]byte, len(e.X)*2+1) + l := len(e.X) + bytes[0] = 0x04 + copy(bytes[1:], e.X) + copy(bytes[l+1:], e.Y) + return a.C.NewG1FromBytes(bytes) +} + +func (a *Fp256bn) G2ToProto(g2 *math.G2) *ECP2 { + if g2 == nil { + panic("nil argument") + } + + bytes := g2.Bytes() + l := len(bytes) / 4 + return &ECP2{ + Xa: bytes[0:l], + Xb: bytes[l : 2*l], + Ya: bytes[2*l : 3*l], + Yb: bytes[3*l:], + } + +} + +func (a *Fp256bn) G2FromProto(e *ECP2) (*math.G2, error) { + if e == nil { + return nil, fmt.Errorf("nil argument") + } + + if len(e.Xa) != a.C.CoordByteSize || len(e.Xb) != a.C.CoordByteSize || len(e.Ya) != a.C.CoordByteSize || len(e.Yb) != a.C.CoordByteSize { + return nil, fmt.Errorf("invalid marshalled length") + } + + bytes := make([]byte, len(e.Xa)*4) + l := len(e.Xa) + copy(bytes[0:l], e.Xa) + copy(bytes[l:2*l], e.Xb) + copy(bytes[2*l:3*l], e.Ya) + copy(bytes[3*l:], e.Yb) + return a.C.NewG2FromBytes(bytes) +} + +type Fp256bnMiracl struct { + C *math.Curve +} + +func (a *Fp256bnMiracl) G1ToProto(g1 *math.G1) *ECP { + if g1 == nil { + panic("nil argument") + } + + bytes := g1.Bytes()[1:] + l := len(bytes) / 2 + return &ECP{ + X: bytes[:l], + Y: bytes[l:], + } +} + +func (a *Fp256bnMiracl) G1FromRawBytes(raw []byte) (*math.G1, error) { + l := len(raw) / 2 + + return a.G1FromProto(&ECP{ + X: raw[:l], + Y: raw[l:], + }) +} + +func (a *Fp256bnMiracl) G1FromProto(e *ECP) (*math.G1, error) { + if e == nil { + return nil, fmt.Errorf("nil argument") + } + + if len(e.X) != a.C.CoordByteSize || len(e.Y) != a.C.CoordByteSize { + return nil, fmt.Errorf("invalid marshalled length") + } + + bytes := make([]byte, len(e.X)*2+1) + l := len(e.X) + bytes[0] = 0x04 + copy(bytes[1:], e.X) + copy(bytes[l+1:], e.Y) + return a.C.NewG1FromBytes(bytes) +} + +func (a *Fp256bnMiracl) G2ToProto(g2 *math.G2) *ECP2 { + if g2 == nil { + panic("nil argument") + } + + bytes := g2.Bytes()[1:] + l := len(bytes) / 4 + return &ECP2{ + Xa: bytes[0:l], + Xb: bytes[l : 2*l], + Ya: bytes[2*l : 3*l], + Yb: bytes[3*l:], + } + +} + +func (a *Fp256bnMiracl) G2FromProto(e *ECP2) (*math.G2, error) { + if e == nil { + return nil, fmt.Errorf("nil argument") + } + + if len(e.Xa) != a.C.CoordByteSize || len(e.Xb) != a.C.CoordByteSize || len(e.Ya) != a.C.CoordByteSize || len(e.Yb) != a.C.CoordByteSize { + return nil, fmt.Errorf("invalid marshalled length") + } + + bytes := make([]byte, 1+len(e.Xa)*4) + bytes[0] = 0x04 + l := len(e.Xa) + copy(bytes[1:], e.Xa) + copy(bytes[1+l:], e.Xb) + copy(bytes[1+2*l:], e.Ya) + copy(bytes[1+3*l:], e.Yb) + return a.C.NewG2FromBytes(bytes) +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn_test.go b/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn_test.go new file mode 100644 index 0000000..7849392 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/fp256bn_test.go @@ -0,0 +1,158 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package amcl + +import ( + "io/ioutil" + "testing" + + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +func TestFp256bnTranslatorGen(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + tr := &Fp256bn{ + C: curve, + } + + genG1 := curve.GenG1 + ecp := tr.G1ToProto(genG1) + p, err := tr.G1FromProto(ecp) + assert.True(t, p.Equals(genG1)) + assert.NoError(t, err) + + genG2 := curve.GenG2 + ecp2 := tr.G2ToProto(genG2) + p2, err := tr.G2FromProto(ecp2) + assert.True(t, p2.Equals(genG2)) + assert.NoError(t, err) +} + +func TestFp256bnTranslatorRndG1(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + tr := &Fp256bn{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG1 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G1ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G1FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} + +func TestFp256bnTranslatorRndG2(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + tr := &Fp256bn{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG2 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G2ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G2FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} + +func TestFp256bnMiraclTranslatorGen(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + tr := &Fp256bnMiracl{ + C: curve, + } + + genG1 := curve.GenG1 + ecp := tr.G1ToProto(genG1) + p, err := tr.G1FromProto(ecp) + assert.True(t, p.Equals(genG1)) + assert.NoError(t, err) + + genG2 := curve.GenG2 + ecp2 := tr.G2ToProto(genG2) + p2, err := tr.G2FromProto(ecp2) + assert.True(t, p2.Equals(genG2)) + assert.NoError(t, err) +} + +func TestFp256bnMiraclTranslatorRndG1(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + tr := &Fp256bnMiracl{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG1 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G1ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G1FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} + +func TestFp256bnMiraclTranslatorRndG2(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL_MIRACL] + tr := &Fp256bnMiracl{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG2 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G2ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G2FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} + +func TestFp256bnTranslatorG2FromFile(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + tr := &Fp256bn{ + C: curve, + } + + wBytes, err := ioutil.ReadFile("./testdata/old/g2.bytes") + assert.NoError(t, err) + wProtoBytes, err := ioutil.ReadFile("./testdata/old/g2.proto.bytes") + assert.NoError(t, err) + + ecp := &ECP2{} + err = proto.Unmarshal(wProtoBytes, ecp) + assert.NoError(t, err) + + h1, err := tr.G2FromProto(ecp) + assert.Equal(t, wBytes, h1.Bytes()) + assert.NoError(t, err) +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy.go b/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy.go new file mode 100644 index 0000000..2be82f2 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy.go @@ -0,0 +1,64 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package amcl + +import ( + math "github.com/IBM/mathlib" +) + +type Gurvy struct { + C *math.Curve +} + +func (a *Gurvy) G1ToProto(g1 *math.G1) *ECP { + bytes := g1.Bytes() + + l := len(bytes) / 2 + return &ECP{ + X: bytes[:l], + Y: bytes[l:], + } +} + +func (a *Gurvy) G1FromRawBytes(raw []byte) (*math.G1, error) { + l := len(raw) / 2 + + return a.G1FromProto(&ECP{ + X: raw[:l], + Y: raw[l:], + }) +} + +func (a *Gurvy) G1FromProto(e *ECP) (*math.G1, error) { + bytes := make([]byte, len(e.X)*2) + l := len(e.X) + copy(bytes, e.X) + copy(bytes[l:], e.Y) + return a.C.NewG1FromBytes(bytes) +} + +func (a *Gurvy) G2ToProto(g2 *math.G2) *ECP2 { + bytes := g2.Bytes() + l := len(bytes) / 4 + return &ECP2{ + Xa: bytes[0:l], + Xb: bytes[l : 2*l], + Ya: bytes[2*l : 3*l], + Yb: bytes[3*l:], + } + +} + +func (a *Gurvy) G2FromProto(e *ECP2) (*math.G2, error) { + bytes := make([]byte, len(e.Xa)*4) + l := len(e.Xa) + copy(bytes[0:l], e.Xa) + copy(bytes[l:2*l], e.Xb) + copy(bytes[2*l:3*l], e.Ya) + copy(bytes[3*l:], e.Yb) + return a.C.NewG2FromBytes(bytes) +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy_test.go b/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy_test.go new file mode 100644 index 0000000..c14999a --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/gurvy_test.go @@ -0,0 +1,75 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package amcl + +import ( + "testing" + + math "github.com/IBM/mathlib" + "github.com/stretchr/testify/assert" +) + +func TestGurvyTranslatorGen(t *testing.T) { + curve := math.Curves[math.BN254] + tr := &Gurvy{ + C: curve, + } + + genG1 := curve.GenG1 + ecp := tr.G1ToProto(genG1) + p, err := tr.G1FromProto(ecp) + assert.NoError(t, err) + assert.True(t, p.Equals(genG1)) + + genG2 := curve.GenG2 + ecp2 := tr.G2ToProto(genG2) + p2, err := tr.G2FromProto(ecp2) + assert.True(t, p2.Equals(genG2)) + assert.NoError(t, err) +} + +func TestGurvyTranslatorRndG1(t *testing.T) { + curve := math.Curves[math.BN254] + tr := &Gurvy{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG1 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G1ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G1FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} + +func TestGurvyTranslatorRndG2(t *testing.T) { + curve := math.Curves[math.BN254] + tr := &Gurvy{ + C: curve, + } + + rnd, err := curve.Rand() + assert.NoError(t, err) + + g := curve.GenG2 + r := curve.NewRandomZr(rnd) + h := g.Mul(r) + + ecp := tr.G2ToProto(h) + assert.NotNil(t, ecp) + + h1, err := tr.G2FromProto(ecp) + assert.True(t, h.Equals(h1)) + assert.NoError(t, err) +} diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.bytes b/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.bytes new file mode 100644 index 0000000..1eb8c97 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.bytes @@ -0,0 +1 @@ +u`gU7$>>SXѰ`S,B4ImǍ/E T7?m4rŪkz:&/Q[^i&~D-򝥕z3YiU_/w. \ No newline at end of file diff --git a/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.proto.bytes b/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.proto.bytes new file mode 100644 index 0000000..e9fd073 --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/translator/amcl/testdata/old/g2.proto.bytes @@ -0,0 +1,2 @@ + + u`gU7$>>SXѰ`S ,B4ImǍ/E T7?m4r Ūkz:&/Q[^i&~" D-򝥕z3YiU_/w. \ No newline at end of file diff --git a/v2/bccsp/schemes/dlog/crypto/util.go b/v2/bccsp/schemes/dlog/crypto/util.go new file mode 100644 index 0000000..31261fe --- /dev/null +++ b/v2/bccsp/schemes/dlog/crypto/util.go @@ -0,0 +1,71 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "io" + + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + math "github.com/IBM/mathlib" +) + +func appendBytes(data []byte, index int, bytesToAdd []byte) int { + copy(data[index:], bytesToAdd) + return index + len(bytesToAdd) +} +func appendBytesG1(data []byte, index int, E *math.G1) int { + return appendBytes(data, index, E.Bytes()) +} +func appendBytesG2(data []byte, index int, E *math.G2) int { + return appendBytes(data, index, E.Bytes()) +} +func appendBytesBig(data []byte, index int, B *math.Zr) int { + return appendBytes(data, index, B.Bytes()) +} +func appendBytesString(data []byte, index int, s string) int { + bytes := []byte(s) + copy(data[index:], bytes) + return index + len(bytes) +} + +// MakeNym creates a new unlinkable pseudonym +func (i *Idemix) MakeNym(sk *math.Zr, IPk *IssuerPublicKey, rng io.Reader, t Translator) (*math.G1, *math.Zr, error) { + return makeNym(sk, IPk, rng, i.Curve, t) +} + +func makeNym(sk *math.Zr, IPk *IssuerPublicKey, rng io.Reader, curve *math.Curve, t Translator) (*math.G1, *math.Zr, error) { + // Construct a commitment to the sk + // Nym = h_{sk}^sk \cdot h_r^r + RandNym := curve.NewRandomZr(rng) + HSk, err := t.G1FromProto(IPk.HSk) + if err != nil { + return nil, nil, err + } + HRand, err := t.G1FromProto(IPk.HRand) + if err != nil { + return nil, nil, err + } + Nym := HSk.Mul2(sk, HRand, RandNym) + return Nym, RandNym, nil +} + +func (i *Idemix) MakeNymFromBytes(raw []byte) (*math.G1, *math.Zr, error) { + return makeNymFromBytes(i.Curve, raw, i.Translator) +} + +func makeNymFromBytes(curve *math.Curve, raw []byte, translator Translator) (*math.G1, *math.Zr, error) { + RandNym := curve.NewZrFromBytes(raw[:curve.ScalarByteSize]) + pk, err := translator.G1FromProto(&amcl.ECP{ + X: raw[curve.ScalarByteSize : curve.ScalarByteSize+curve.CoordByteSize], + Y: raw[curve.ScalarByteSize+curve.CoordByteSize:], + }) + if err != nil { + return nil, nil, err + } + + return pk, RandNym, nil +} diff --git a/v2/bccsp/smartcard_test.go b/v2/bccsp/smartcard_test.go new file mode 100644 index 0000000..cc178a5 --- /dev/null +++ b/v2/bccsp/smartcard_test.go @@ -0,0 +1,485 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix_test + +import ( + "crypto/aes" + "crypto/rand" + "encoding/hex" + "os" + "testing" + + "github.com/IBM/idemix/bccsp/schemes/aries" + "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp" + "github.com/IBM/idemix/v2/bccsp/handlers" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + "github.com/IBM/idemix/v2/idemixmsp" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/hyperledger/aries-bbs-go/bbs" + "github.com/stretchr/testify/assert" +) + +func getSmartcard(t *testing.T) (*aries.Smartcard, *math.Curve) { + c := math.Curves[math.FP256BN_AMCL_MIRACL] + + rng, err := c.Rand() + assert.NoError(t, err) + + k0, err := hex.DecodeString("4669650c993c43ef45742c3aa8aeb842") + assert.NoError(t, err) + k1, err := hex.DecodeString("358abd275e6a945d680d40474f5f16c7") + assert.NoError(t, err) + + ciph0, err := aes.NewCipher(k0) + assert.NoError(t, err) + ciph1, err := aes.NewCipher(k1) + assert.NoError(t, err) + + h0Bytes, err := hex.DecodeString("0441c48875b5045400ce6bb4ce5b9c733f6d539a89f1ec2c24e0e04f56932c52ffd918f0679996b017363c591df413e1ac0be63e919defd6edc0686d41b1fcd68d") + assert.NoError(t, err) + h0, err := c.NewG1FromBytes(h0Bytes) + assert.NoError(t, err) + + h1Bytes, err := hex.DecodeString("041e304080e9afd0d04317d12b5cb058cd4f322a1cddb71e64a47528353d51f7a8324fce4698dff52cd8d4c7dd2c8c94c6fba8ce12493d182e4d849106dc5c46de") + assert.NoError(t, err) + h1, err := c.NewG1FromBytes(h1Bytes) + assert.NoError(t, err) + + h2Bytes, err := hex.DecodeString("04196c48c6d0249de961b97433a577da537c341ad0ea0cde4dfa40ef6bab9b59f274a07a3665518401119957a52a32a18256d7215e4f1d0ce6c9e2646d939c07f9") + assert.NoError(t, err) + h2, err := c.NewG1FromBytes(h2Bytes) + assert.NoError(t, err) + + eidBytes, err := hex.DecodeString("003522e297a5f7db7521e23d9f9b87378126acd80429cf4ec07344f06bd9f7d5") + assert.NoError(t, err) + eid := c.NewZrFromBytes(eidBytes) + + skBytes, err := hex.DecodeString("00f022e297a5f7db7521e23d9f9b87378182acd80429cf4ec07344f06bd9f7d5") + assert.NoError(t, err) + sk := c.NewZrFromBytes(skBytes) + + return &aries.Smartcard{ + H0: h0, + H1: h1, + H2: h2, + Uid_sk: sk, + EID: eid, + PRF_K0: ciph0, + PRF_K1: ciph1, + Curve: c, + Rng: rng, + }, c +} + +func readFile(t *testing.T, name string) []byte { + bytes, err := os.ReadFile(name) + assert.NoError(t, err) + + return bytes +} + +func TestSmartcardHybrid(t *testing.T) { + sc, curve := getSmartcard(t) + translator := &amcl.Gurvy{C: curve} + + /*******************************************************************************/ + /****************************read out idemix config*****************************/ + /*******************************************************************************/ + + issuer := &aries.Issuer{Curve: curve} + + _ipk, err := issuer.NewPublicKeyFromBytes(readFile(t, "testdata/idemix/msp/IssuerPublicKey"), []string{"", "", "", ""}) + assert.NoError(t, err) + + conf := &idemixmsp.IdemixMSPSignerConfig{} + err = proto.Unmarshal(readFile(t, "testdata/idemix/user/SignerConfig"), conf) + assert.NoError(t, err) + + /*******************************************************************************/ + /*****************instantiate an idemix bccsp and import ipk********************/ + /*******************************************************************************/ + + CSP, err := idemix.NewAries(NewDummyKeyStore(), curve, translator, true) + assert.NoError(t, err) + + IssuerPublicKey, err := CSP.KeyImport(readFile(t, "testdata/idemix/msp/IssuerPublicKey"), &types.IdemixIssuerPublicKeyImportOpts{Temporary: true, AttributeNames: []string{"", "", "", ""}}) + assert.NoError(t, err) + + /*******************************************************************************/ + /**************configure the smartcard to work with these creds*****************/ + /*******************************************************************************/ + + ipk := _ipk.(*aries.IssuerPublicKey) + sc.H0 = ipk.PKwG.H0 + sc.H1 = ipk.PKwG.H[0] + sc.H2 = ipk.PKwG.H[3] + sc.EID = bbs.FrFromOKM([]byte(conf.EnrollmentId), curve) + sc.Uid_sk = curve.NewZrFromBytes(conf.Sk) + + /*******************************************************************************/ + /**************************NYM SIGNATURE****************************************/ + /*******************************************************************************/ + + scIdmx := &aries.SmartcardIdemixBackend{Curve: curve} + + msg := []byte("msg") + rNymEid, nymEid := sc.NymEid() + + /*****sign low-level*****/ + + sig, nym, rNym, err := scIdmx.Sign(sc, ipk, msg) + assert.NoError(t, err) + + /*****verify with csp*****/ + + valid, err := CSP.Verify(handlers.NewNymPublicKey(nil, nil), sig, msg, &types.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + IsSmartcard: true, + NymEid: nymEid, + }) + assert.NoError(t, err) + assert.True(t, valid) + + /*******************************************************************************/ + /**************************IDEMIX SIGNATURE*************************************/ + /*******************************************************************************/ + + rhIndex, eidIndex := 3, 2 + + idemixAttrs := []types.IdemixAttribute{ + { + Type: types.IdemixBytesAttribute, + Value: []byte(conf.OrganizationalUnitIdentifier), + }, + { + Type: types.IdemixIntAttribute, + Value: int(conf.Role), + }, + { + Type: types.IdemixHiddenAttribute, + }, + { + Type: types.IdemixHiddenAttribute, + }, + } + + meta := &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + EidNymAuditData: &types.AttrNymAuditData{ + Nym: nymEid, + Rand: rNymEid, + Attr: bbs.FrFromOKM([]byte(conf.EnrollmentId), curve), + }, + } + + signer := &aries.Signer{ + Curve: sc.Curve, + Rng: rand.Reader, + } + + /*****sign low-level*****/ + + sig, _, err = signer.Sign(conf.Cred, nil, nym, rNym, ipk, idemixAttrs, nil, rhIndex, eidIndex, nil, types.Smartcard, meta) + assert.NoError(t, err) + + /*****verify with csp*****/ + + valid, err = CSP.Verify( + IssuerPublicKey, + sig, + nil, + &types.IdemixSignerOpts{ + Attributes: idemixAttrs, + RhIndex: 3, + EidIndex: 2, + VerificationType: types.ExpectSmartcard, + Metadata: &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + }, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) +} + +func TestSmartcardCSP(t *testing.T) { + sc, curve := getSmartcard(t) + translator := &amcl.Gurvy{C: curve} + + /*******************************************************************************/ + /****************************read out idemix config*****************************/ + /*******************************************************************************/ + + issuer := &aries.Issuer{Curve: curve} + + _ipk, err := issuer.NewPublicKeyFromBytes(readFile(t, "testdata/idemix/msp/IssuerPublicKey"), []string{"", "", "", ""}) + assert.NoError(t, err) + + conf := &idemixmsp.IdemixMSPSignerConfig{} + err = proto.Unmarshal(readFile(t, "testdata/idemix/user/SignerConfig"), conf) + assert.NoError(t, err) + + /*******************************************************************************/ + /*****************instantiate an idemix bccsp and import ipk********************/ + /*******************************************************************************/ + + CSP, err := idemix.NewAries(NewDummyKeyStore(), curve, translator, true) + assert.NoError(t, err) + + IssuerPublicKey, err := CSP.KeyImport(readFile(t, "testdata/idemix/msp/IssuerPublicKey"), &types.IdemixIssuerPublicKeyImportOpts{Temporary: true, AttributeNames: []string{"", "", "", ""}}) + assert.NoError(t, err) + + /*******************************************************************************/ + /**************configure the smartcard to work with these creds*****************/ + /*******************************************************************************/ + + ipk := _ipk.(*aries.IssuerPublicKey) + sc.H0 = ipk.PKwG.H0 + sc.H1 = ipk.PKwG.H[0] + sc.H2 = ipk.PKwG.H[3] + sc.EID = bbs.FrFromOKM([]byte(conf.EnrollmentId), curve) + sc.Uid_sk = curve.NewZrFromBytes(conf.Sk) + + /*******************************************************************************/ + /**************************NYM SIGNATURE****************************************/ + /*******************************************************************************/ + + msg := []byte("msg") + rNymEid, nymEid := sc.NymEid() + + /*****sign*****/ + + opts := &types.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + IsSmartcard: true, + Smartcard: sc, + } + + sig, err := CSP.Sign(handlers.NewUserSecretKey(nil, true), msg, opts) + assert.NoError(t, err) + + /*****verify*****/ + + valid, err := CSP.Verify(handlers.NewNymPublicKey(nil, nil), sig, msg, &types.IdemixNymSignerOpts{ + IssuerPK: IssuerPublicKey, + IsSmartcard: true, + NymEid: nymEid, + }) + assert.NoError(t, err) + assert.True(t, valid) + + /*******************************************************************************/ + /**************************IDEMIX SIGNATURE*************************************/ + /*******************************************************************************/ + + /*****sign*****/ + + nymsk, err := handlers.NewNymSecretKey(opts.RNym, opts.NymG1, translator, true) + assert.NoError(t, err) + + signOpts := &types.IdemixSignerOpts{ + Credential: conf.Cred, + Nym: nymsk, + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + { + Type: types.IdemixBytesAttribute, + Value: []byte(conf.OrganizationalUnitIdentifier), + }, + { + Type: types.IdemixIntAttribute, + Value: int(conf.Role), + }, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + SigType: types.Smartcard, + Metadata: &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + EidNymAuditData: &types.AttrNymAuditData{ + Nym: nymEid, + Rand: rNymEid, + Attr: bbs.FrFromOKM([]byte(conf.EnrollmentId), curve), + }, + }, + } + + signature, err := CSP.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + signOpts, + ) + assert.NoError(t, err) + + /*****verify*****/ + + valid, err = CSP.Verify( + IssuerPublicKey, + signature, + nil, + &types.IdemixSignerOpts{ + Attributes: []types.IdemixAttribute{ + { + Type: types.IdemixBytesAttribute, + Value: []byte(conf.OrganizationalUnitIdentifier), + }, + { + Type: types.IdemixIntAttribute, + Value: int(conf.Role), + }, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + VerificationType: types.ExpectSmartcard, + Metadata: &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + }, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + /*******************************************************************************/ + + /*****sign*****/ + + nymsk, err = handlers.NewNymSecretKey(opts.RNym, opts.NymG1, translator, true) + assert.NoError(t, err) + + signOpts = &types.IdemixSignerOpts{ + Credential: conf.Cred, + Nym: nymsk, + IssuerPK: IssuerPublicKey, + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + SigType: types.Smartcard, + Metadata: &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + EidNymAuditData: &types.AttrNymAuditData{ + Nym: nymEid, + Rand: rNymEid, + Attr: bbs.FrFromOKM([]byte(conf.EnrollmentId), curve), + }, + }, + } + + signature, err = CSP.Sign( + handlers.NewUserSecretKey(nil, false), + nil, + signOpts, + ) + assert.NoError(t, err) + + /*****verify*****/ + + valid, err = CSP.Verify( + IssuerPublicKey, + signature, + nil, + &types.IdemixSignerOpts{ + Attributes: []types.IdemixAttribute{ + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + {Type: types.IdemixHiddenAttribute}, + }, + RhIndex: 3, + EidIndex: 2, + VerificationType: types.ExpectSmartcard, + Metadata: &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + }, + }, + ) + assert.NoError(t, err) + assert.True(t, valid) + + /*******************************************************************************/ + /***********************low-level invocation************************************/ + /*******************************************************************************/ + + rhIndex, eidIndex := 3, 2 + + idemixAttrs := []types.IdemixAttribute{ + { + Type: types.IdemixHiddenAttribute, + }, + { + Type: types.IdemixIntAttribute, + Value: int(conf.Role), + }, + { + Type: types.IdemixHiddenAttribute, + }, + { + Type: types.IdemixHiddenAttribute, + }, + } + + meta := &types.IdemixSignerMetadata{ + EidNym: nymEid.Bytes(), + EidNymAuditData: &types.AttrNymAuditData{ + Nym: nymEid, + Rand: rNymEid, + Attr: bbs.FrFromOKM([]byte(conf.EnrollmentId), curve), + }, + } + + rand, err := sc.Curve.Rand() + assert.NoError(t, err) + + signer := &aries.Signer{ + Curve: sc.Curve, + Rng: rand, + } + + /*****sign*****/ + + sig, _, err = signer.Sign(conf.Cred, nil, opts.NymG1, opts.RNym, ipk, idemixAttrs, nil, rhIndex, eidIndex, nil, types.Smartcard, meta) + assert.NoError(t, err) + + rng, err := curve.Rand() + assert.NoError(t, err) + + /*****verify*****/ + + verifier := &aries.Signer{ + Curve: curve, + Rng: rng, + } + err = verifier.Verify(ipk, sig, nil, []types.IdemixAttribute{ + { + Type: types.IdemixHiddenAttribute, + }, + { + Type: types.IdemixIntAttribute, + Value: int(conf.Role), + }, + { + Type: types.IdemixHiddenAttribute, + }, + { + Type: types.IdemixHiddenAttribute, + }, + }, 3, 2, 0, nil, -1, types.ExpectSmartcard, &types.IdemixSignerMetadata{EidNym: nymEid.Bytes()}) + assert.NoError(t, err) + + /*******************************************************************************/ + /*******************************************************************************/ +} diff --git a/v2/bccsp/testdata/idemix/ca/IssuerPublicKey b/v2/bccsp/testdata/idemix/ca/IssuerPublicKey new file mode 100644 index 0000000..dfbb020 --- /dev/null +++ b/v2/bccsp/testdata/idemix/ca/IssuerPublicKey @@ -0,0 +1 @@ +}rrB^*¾\VY."'Y緶ʍ#i s9n♡d?`@@x \ No newline at end of file diff --git a/v2/bccsp/testdata/idemix/ca/IssuerSecretKey b/v2/bccsp/testdata/idemix/ca/IssuerSecretKey new file mode 100644 index 0000000..d83b4f9 --- /dev/null +++ b/v2/bccsp/testdata/idemix/ca/IssuerSecretKey @@ -0,0 +1 @@ +-z#AFVP\:Ni \ No newline at end of file diff --git a/v2/bccsp/testdata/idemix/ca/RevocationKey b/v2/bccsp/testdata/idemix/ca/RevocationKey new file mode 100644 index 0000000..6777803 --- /dev/null +++ b/v2/bccsp/testdata/idemix/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDBvIARacTRedf41rk7begOTj+oAgH8G87H6+h1riXgwp6PS8d2Bq7PO +Me4GxsAhZBegBwYFK4EEACKhZANiAATRJZTooqayA4MiwC2p9Of4XXqBCryNFHwW +wOcyM1nQpaQsZXr7SmC63el7SPV0M9CFbkagGwvqGPYLtK5sP9TdOGLu1wctvBpL +Q5jvKFLrP1/kebee1JVLeSCvlybi7ww= +-----END PRIVATE KEY----- diff --git a/v2/bccsp/testdata/idemix/msp/IssuerPublicKey b/v2/bccsp/testdata/idemix/msp/IssuerPublicKey new file mode 100644 index 0000000..dfbb020 --- /dev/null +++ b/v2/bccsp/testdata/idemix/msp/IssuerPublicKey @@ -0,0 +1 @@ +}rrB^*¾\VY."'Y緶ʍ#i s9n♡d?`@@x \ No newline at end of file diff --git a/v2/bccsp/testdata/idemix/msp/RevocationPublicKey b/v2/bccsp/testdata/idemix/msp/RevocationPublicKey new file mode 100644 index 0000000..1d65405 --- /dev/null +++ b/v2/bccsp/testdata/idemix/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE0SWU6KKmsgODIsAtqfTn+F16gQq8jRR8 +FsDnMjNZ0KWkLGV6+0pgut3pe0j1dDPQhW5GoBsL6hj2C7SubD/U3Thi7tcHLbwa +S0OY7yhS6z9f5Hm3ntSVS3kgr5cm4u8M +-----END PUBLIC KEY----- diff --git a/v2/bccsp/testdata/idemix/user/SignerConfig b/v2/bccsp/testdata/idemix/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..11b2763d34d1c1d91b62301ec7121f1f991f2abf GIT binary patch literal 561 zcmd;j&B&F=+$)n~+ga28Z?mpgWpc)8g`elnZ3|AF_^9>yt_#aGJ*F6{+ugeKciM)e zSG)%EZA}0E?yM+X$MXcPyMm(e{*od`bQ%E zCBLqJdLk&K@bdcYieA3yyS{DV+PZpO>eWXx90fi+{I_4iegUs>p;nrZ0t0@)D5S7( zs^vA^(;@bDYj;iizVzXZKe{`LV*K77xcD_wHTB5sy+R7tGm^OHEl59fbV^RCP=?WK zPKUD1TMl;q)8Cx-^%Ae_c_9VK%dN-n-Ea-HRNS&QUDn{Yy5f)MQ}ar6GgFM-G72>^viL1a2ruqB5OO=};W6_Y!aG&& zRy=CIVA^>p^R3C^x-9K~JjMZAPUa|Rgz<;hXiT&^ClfqNXn%{%B{i{aod@RqW(|3~ z=W@rP2~O{pd1}^en&0iSuc1k5#^wh?je=flS!Nd~xII7Ww0Lt_Id7?upIzYl(<-IC zi4h(zIkz7|di4o*FX0{mc&5#()2KyM33LW|*GbT{64UMB>5xPkbSU sj_aOq_izY4W-{x#`DwmR)($HXU1HJ6rR1an0A@b@pa1{> literal 0 HcmV?d00001 diff --git a/v2/bccsp/testdata/old/cred_request.sign b/v2/bccsp/testdata/old/cred_request.sign new file mode 100644 index 0000000..1f3f429 --- /dev/null +++ b/v2/bccsp/testdata/old/cred_request.sign @@ -0,0 +1,4 @@ + +D + r~?+԰1XO篸Tϵ}6 =5=A +uc{FE[%$ >|"21]>$O ʇ`vʽ+ ]:CA|!?گPq" 4!Jj~2E/0=?~uJN \ No newline at end of file diff --git a/v2/bccsp/testdata/old/credential.sign b/v2/bccsp/testdata/old/credential.sign new file mode 100644 index 0000000000000000000000000000000000000000..73bf01fc3162b1d27d276008416781bbc3d0d171 GIT binary patch literal 378 zcmd;b;Zk^^DOs^bR&^$O?~F3P;^+O__54)I=ky%q_ElA_IjuDHs@J|fTr*;9*r;ew4Iq0q7qa!`$Vy_&eh!q&l_`hzFf0KvT07%0>-Zg-duk8zkD8_ltStf&)%Xr zH)7SUKU}b*hNGaWY|3Q$#fqkx-v3%9tNGlO6q@RMJNo9-sdM~O?B1$yX;YBO?>T3t zr`m09;alqAJ$;jwLY@itod27vX3SaY@@#hnmrlh5S^LfA7GT)3dt&r529nrymv=mtV?j5|KD4*p2mS@k(D?c5$`8;KuOSC>6-LU%g Jhm#X-008v5o}mB$ literal 0 HcmV?d00001 diff --git a/v2/bccsp/testdata/old/cri.sign b/v2/bccsp/testdata/old/cri.sign new file mode 100644 index 0000000000000000000000000000000000000000..df983410446c66e748e636f1af9b18c2e3ac2d7f GIT binary patch literal 244 zcmV;l?-E6S*Yac;bi9HHp$`?KGo)YAYHba3DtK#X_UAc6SYR5>7r*@5>~0jbT_w z=?S!fzT7AOA|M4+d+Z7mgd--E}}j=n;XK}X3Z04tWDn)0x|"21]>$O ʇ`vʽ+ \ No newline at end of file diff --git a/v2/bccsp/testdata/old/issuerkey.pk b/v2/bccsp/testdata/old/issuerkey.pk new file mode 100644 index 0000000000000000000000000000000000000000..3335e2487f895aea15cf84c662d7bc20bb01e651 GIT binary patch literal 906 zcmV;519kig1wnLlaxn@8L3DI-G71GjbaZkv3I#!QbaFHb1wnLlay1e}3Lr$&J*!t6 zx-WI#k<7}M`HsaJLU#z2`^tX71bhLHOK1`xQB=H?c(?kNL3dIGN5~AWSvtXGd?Pc^ z!Yr^c9T=7$8bk^ph9Le2uSxUWQuVx903(Sv;tK5U?4+V=P<>X2bJ@j~5+IK5lbAhx zo!1^}_R@MzbiOm{@A1-@ivh-CB^*@AHG?8V3Ls}hpJvX(H|EK3-drhr3AEp`W2FgB zgd{zLl$$p+?{g9$!kJ|gVp(n>)@zh3m@O_OWYmRy8>B~l2i02$0;j-h5qGweX;n*VXlYYB~t$R5+F!-^ihApda{PK(`kq6&6=q5cNk>v zr|dizuud4XS6w1R3Ltt#7{iv zF-W7i@$=h#+GHkh{aInWjc`4rGspLgbE8`~B18%x`)K#Mxy47%fC2YpD-AbEgK2WN z@`CMqkuhJcaJ?@@5+DNfC5fY=Vjg%K3Z|lFUV^UuVYJvnU4q*0<1jKPfJP!j3LuzV zEQNKn;UV#;6wPL?Q1qKls9g^OJ0p}z1smf6(OwcDsh&y}wsu%v%8Grbn>3W2WD6z~ z)Jq?=1B-ix1OrScX$CEHO2A}TIl2KWF)r?(*G8to=|8!bUL8{`4S*h zGJ(;p((0{4s literal 0 HcmV?d00001 diff --git a/v2/bccsp/testdata/old/issuerkey.sk b/v2/bccsp/testdata/old/issuerkey.sk new file mode 100644 index 0000000000000000000000000000000000000000..5283435bbb44c7c282319ac91651a0cb35e6efce GIT binary patch literal 943 zcmV;g15o@5AOl&pi_6pmZq8?=)w!am&DonhV_z&%;!^B_Zh0K|e-erZ3I#!QbaF8Y z1wnLlaxw}9L3DI-GYSPkbaZkw3I#!QbaFKkL<%58(><$K8@exb-;vD9m-&vx8bWsn zmHWzm!32B(k4tD0AW>Ajlz6xLmO*z?1xLsXu30+4Wqczu(84URF&!9|9~wjoAci3R z2d_!<-BR_uSpXx6H{uHH?(C$ZYfybwh;!M+mJ%S2?vt23e4W=GYWC84PISIA>hJN= zn2Q0%Vqgp`{%H1Bf~Ai|kt z6Jl9zA=Yb@ESN1WBxKZueH)}leF*DT7Q99TBO*i!AVdT_eY&1Fuq!rZI>#w1fQA0$ zdVR6@$6>CA-z8H1`4S*Vcl1$z!g{iXwbN;b>&=>|^LH3z@2BiM7qCtkv{zjsL<%5! zMdd8&KZmxc06DENOf=9>k6wDcli8_6ql{h+`YzuRAnB!bG%-k{x$*PcecEIuaQ#_f zyp3=@q%+6&jB}$~HzGs|Ap2{b97&LS2H|@8d8sD1b&HL<%67Tr7okwBaG~s1(g+u2A%w zPN-cE13M#>N(CF^0?}R)AgP{87PfX+UdoDnr<*jCon#9p6x2%}wF8TLh6DplODc!~ z3Ly8PN?Hd^E;Q+m8+%|r9Cg_*JONqutwyx0F%sM?u38cxH*hCwsjltW0~7I+MB2ESK6-TYrc`5K~2D{HfOm zSXOi^`_L+8%d)*xDsR8u5+E(4AJIBf_Ax(z{l->W7auHQ??k}^b}~YsJ=P|2HN84S z3LqSv#&>uD9W}-B5L)Qt>|`Xj4ATD=qn=P`Jajst0r?UjR5F3ltP yܰR" 35TlN}3RX6=X \ No newline at end of file diff --git a/v2/bccsp/testdata/old/nymkey.pk b/v2/bccsp/testdata/old/nymkey.pk new file mode 100644 index 0000000..7f41b88 --- /dev/null +++ b/v2/bccsp/testdata/old/nymkey.pk @@ -0,0 +1 @@ +}˰ vEW[eUv0TԮkֻ[9:ra ! _Rw: \ No newline at end of file diff --git a/v2/bccsp/testdata/old/nymkey.sk b/v2/bccsp/testdata/old/nymkey.sk new file mode 100644 index 0000000..37ac7fb --- /dev/null +++ b/v2/bccsp/testdata/old/nymkey.sk @@ -0,0 +1 @@ +!$&vXGu^ՃZ] \ No newline at end of file diff --git a/v2/bccsp/testdata/old/revocation.pk b/v2/bccsp/testdata/old/revocation.pk new file mode 100644 index 0000000000000000000000000000000000000000..d1f02a868c8c96eb49bbe91cc682d1577bb004f7 GIT binary patch literal 120 zcmV-;0EhoDb}$eI2P%e0&OHJF1_djD1OOrfVgLmC?)Ouo@TK#W;-Y}j@M~XNUjivT zJs#dkpoxs<=~iHZpYXXxXc7l_sfBSYW;cu==)wt&qB_iNFlCr z^gZqEqH5VT`Vt_CN}c1k2h{FR?kc#4vk4ch)M5vCUKNF@rVE*664tdEL<%4h!n-zd z&|8vl2}F348VVf*u@|`{(`39A=XtlOD?J4gAYhRAO@AGJZ8ic2oq|a|Ku=ZTEe+RI z?B5NzksAb`Uil^@E= z{72A(JYEcCSXwkOR;CO`zTv!V{$WoIEixcse`+3oIW3pAe~tVBiU%WXL$IJZOP(rF zGXCvLl0C{gAcI-xxH)`{Mn&T*qrKS4tiK~$2kl>c{(b?562aq1KXQu6t|D|wAY}3|{xwaT__i7&PHemSXvYePYi#y?=vftB zzN529sZt;skUxhiQ>eI>3mpUVVrulI`A=cICi@1w%kV9vwrk~5AU6*Ldhcov^Jj7X z8p-uSN|R<3y;t@Pw-dx|_|GWUhf*M|1@zYnI@Hwb_&^_-YaZMX>o3y6O@iqzDXO9* z%!H^?AmK%YaI6V-d9ZH;joI~WG)XL{kv~U!%BImNNDvB_;Zh)DzwfgxLJ1L1iY4X? zA`F{1xVt5EBDRH157U+YlA4BEAc&lW>)9*@rnSH!j%70?4a}R2yI}Rf0mNu{!e0cy z9b!ZZAbrcQ84Mqkc8`+6MI2XKWzki3tT6UeALZ1pxN8Uj))FAQ2wOSA|E?FMI&##_ zVfV+vpdkypv=U#xQg?(pq4Sq&Af{I)_3eYldA4uSMR7n1Hk4uaaS`gdeG_rAWiPkN z26Bi23LyRrGf=e2Y#=CB4_ACBkvh&CQ<@ULggnwF7Pg7Np8FCYPNra2bBe%J+gjnq zH`o)oB;9!8htM^N(rN89qkd{D8X#~WM(4#sqqBB*4RsPuK2h(>Bz28pSV-v!w1K|d zC;uWK1ytj_(}=>5LhhzXDSon_i%h8IK@HfWj literal 0 HcmV?d00001 diff --git a/v2/bccsp/testdata/old/signature_with_disclosed_attribute.sign b/v2/bccsp/testdata/old/signature_with_disclosed_attribute.sign new file mode 100644 index 0000000000000000000000000000000000000000..95ace8d405225335df767f56a394c254b5a6fec5 GIT binary patch literal 901 zcmV;01A6=lL<%6Q)peI`3`_FY%en1n2C%={jCrUf4VL$*h4O}Yzk})$AS6ZR^WC>{ zUt~Oem;=s!0)l2?uS~Wu~>b14^KiaoU5=yB_$-`s~6fDL<%7FeV|*H zpqBlSXx}rR;95g&6aR;H1sdQURaYDr$>C%YAg8GbHcAAy{NkYQK(^y0Q+>^l=r0Q) zk{`|J!err#wjv-X#>A@RC{O|L{PsCd&;FF^JZ00p^b(u5`Zhw%ps8#sAewq;o)-)0 z@N0Yupx5Z+vpp(#T#%DyO3pa+iUnFkXj^SZHjr0 zMT$su4@I?WL)pO_D;<^H|WU#S4@B67l-+-YP<{$fN5AbrcQ84Mqkc8`+6 zMI2XKWzki3tT6UeALZ1pxN8Uj))FAQ2wOSA|E?FMI&##_VfV+vpdkypv=U#xQg?(p zq4Sq&ApYXWboQ7mfwg?LhJ;BFqo-{~U<(bM7nH-tBP)$Y6LN?F3LyRrGf=e2Y#=CB z4_ACBkvh&CQ<@ULggnwF7Pg7Np8FCYPNra2bBe%J+gjnqH`o)oB;9!8htM^N(rN89 zqkd{D8X#~WM(4#sqqBB*4RsPuK2h(>Bz28pSV-v!w1K|dC;uWK1ytj_(}=>5LhhzX zDSon_i%h8IKy*#oF literal 0 HcmV?d00001 diff --git a/v2/bccsp/testdata/old/userkey.sk b/v2/bccsp/testdata/old/userkey.sk new file mode 100644 index 0000000..2e8d477 --- /dev/null +++ b/v2/bccsp/testdata/old/userkey.sk @@ -0,0 +1 @@ +Ah!ğ0oZJPz&Ϲ)" \ No newline at end of file diff --git a/v2/common/flogging/core.go b/v2/common/flogging/core.go new file mode 100644 index 0000000..2e03173 --- /dev/null +++ b/v2/common/flogging/core.go @@ -0,0 +1,125 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "go.uber.org/zap/zapcore" +) + +type Encoding int8 + +const ( + CONSOLE = iota + JSON + LOGFMT +) + +// EncodingSelector is used to determine whether log records are +// encoded as JSON or in human readable CONSOLE or LOGFMT formats. +type EncodingSelector interface { + Encoding() Encoding +} + +// Core is a custom implementation of a zapcore.Core. It's a terrible hack that +// only exists to work around the intersection of state associated with +// encoders, implementation hiding in zapcore, and implicit, ad-hoc logger +// initialization within fabric. +// +// In addition to encoding log entries and fields to a buffer, zap Encoder +// implementations also need to maintain field state. When zapcore.Core.With is +// used, the associated encoder is cloned and the fields are added to the +// encoder. This means that encoder instances cannot be shared across cores. +// +// In terms of implementation hiding, it's difficult for our FormatEncoder to +// cleanly wrap the JSON and console implementations from zap as all methods +// from the zapcore.ObjectEncoder would need to be implemented to delegate to +// the correct backend. +// +// This implementation works by associating multiple encoders with a core. When +// fields are added to the core, the fields are added to all of the encoder +// implementations. The core also references the logging configuration to +// determine the proper encoding to use, the writer to delegate to, and the +// enabled levels. +type Core struct { + zapcore.LevelEnabler + Levels *LoggerLevels + Encoders map[Encoding]zapcore.Encoder + Selector EncodingSelector + Output zapcore.WriteSyncer + Observer Observer +} + +//go:generate counterfeiter -o mock/observer.go -fake-name Observer . Observer + +type Observer interface { + Check(e zapcore.Entry, ce *zapcore.CheckedEntry) + WriteEntry(e zapcore.Entry, fields []zapcore.Field) +} + +func (c *Core) With(fields []zapcore.Field) zapcore.Core { + clones := map[Encoding]zapcore.Encoder{} + for name, enc := range c.Encoders { + clone := enc.Clone() + addFields(clone, fields) + clones[name] = clone + } + + return &Core{ + LevelEnabler: c.LevelEnabler, + Levels: c.Levels, + Encoders: clones, + Selector: c.Selector, + Output: c.Output, + Observer: c.Observer, + } +} + +func (c *Core) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.Observer != nil { + c.Observer.Check(e, ce) + } + + if c.Enabled(e.Level) && c.Levels.Level(e.LoggerName).Enabled(e.Level) { + return ce.AddCore(e, c) + } + return ce +} + +func (c *Core) Write(e zapcore.Entry, fields []zapcore.Field) error { + encoding := c.Selector.Encoding() + enc := c.Encoders[encoding] + + buf, err := enc.EncodeEntry(e, fields) + if err != nil { + return err + } + _, err = c.Output.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + + if e.Level >= zapcore.PanicLevel { + c.Sync() + } + + if c.Observer != nil { + c.Observer.WriteEntry(e, fields) + } + + return nil +} + +func (c *Core) Sync() error { + return c.Output.Sync() +} + +func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) { + for i := range fields { + fields[i].AddTo(enc) + } +} diff --git a/v2/common/flogging/core_test.go b/v2/common/flogging/core_test.go new file mode 100644 index 0000000..10d53ad --- /dev/null +++ b/v2/common/flogging/core_test.go @@ -0,0 +1,253 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/IBM/idemix/v2/common/flogging/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +func TestCoreWith(t *testing.T) { + core := &flogging.Core{ + Encoders: map[flogging.Encoding]zapcore.Encoder{}, + Observer: &mock.Observer{}, + } + clone := core.With([]zapcore.Field{zap.String("key", "value")}) + require.Equal(t, core, clone) + + jsonEncoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + consoleEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{}) + core = &flogging.Core{ + Encoders: map[flogging.Encoding]zapcore.Encoder{ + flogging.JSON: jsonEncoder, + flogging.CONSOLE: consoleEncoder, + }, + } + decorated := core.With([]zapcore.Field{zap.String("key", "value")}) + + // verify the objects differ + require.NotEqual(t, core, decorated) + + // verify the objects only differ by the encoded fields + jsonEncoder.AddString("key", "value") + consoleEncoder.AddString("key", "value") + require.Equal(t, core, decorated) +} + +func TestCoreCheck(t *testing.T) { + var enabledArgs []zapcore.Level + levels := &flogging.LoggerLevels{} + err := levels.ActivateSpec("warning") + require.NoError(t, err) + core := &flogging.Core{ + LevelEnabler: zap.LevelEnablerFunc(func(l zapcore.Level) bool { + enabledArgs = append(enabledArgs, l) + return l >= zapcore.WarnLevel + }), + Levels: levels, + } + + // not enabled + ce := core.Check(zapcore.Entry{Level: zapcore.DebugLevel}, nil) + require.Nil(t, ce) + ce = core.Check(zapcore.Entry{Level: zapcore.InfoLevel}, nil) + require.Nil(t, ce) + + // enabled + ce = core.Check(zapcore.Entry{Level: zapcore.WarnLevel}, nil) + require.NotNil(t, ce) + + require.Equal(t, enabledArgs, []zapcore.Level{zapcore.DebugLevel, zapcore.InfoLevel, zapcore.WarnLevel}) +} + +type sw struct { + bytes.Buffer + writeErr error + syncCalled bool + syncErr error +} + +func (s *sw) Sync() error { + s.syncCalled = true + return s.syncErr +} + +func (s *sw) Write(b []byte) (int, error) { + if s.writeErr != nil { + return 0, s.writeErr + } + return s.Buffer.Write(b) +} + +func (s *sw) Encoding() flogging.Encoding { + return flogging.CONSOLE +} + +func TestCoreWrite(t *testing.T) { + encoderConfig := zap.NewDevelopmentEncoderConfig() + encoderConfig.EncodeTime = nil + + output := &sw{} + core := &flogging.Core{ + Encoders: map[flogging.Encoding]zapcore.Encoder{ + flogging.CONSOLE: zapcore.NewConsoleEncoder(encoderConfig), + }, + Selector: output, + Output: output, + } + + entry := zapcore.Entry{ + Level: zapcore.InfoLevel, + Message: "this is a message", + } + err := core.Write(entry, nil) + require.NoError(t, err) + require.Equal(t, "INFO\tthis is a message\n", output.String()) + + output.writeErr = errors.New("super-loose") + err = core.Write(entry, nil) + require.EqualError(t, err, "super-loose") +} + +func TestCoreWriteSync(t *testing.T) { + encoderConfig := zap.NewDevelopmentEncoderConfig() + encoderConfig.EncodeTime = nil + + output := &sw{} + core := &flogging.Core{ + Encoders: map[flogging.Encoding]zapcore.Encoder{ + flogging.CONSOLE: zapcore.NewConsoleEncoder(encoderConfig), + }, + Selector: output, + Output: output, + } + + entry := zapcore.Entry{ + Level: zapcore.DebugLevel, + Message: "no bugs for me", + } + err := core.Write(entry, nil) + require.NoError(t, err) + require.False(t, output.syncCalled) + + entry = zapcore.Entry{ + Level: zapcore.PanicLevel, + Message: "gah!", + } + err = core.Write(entry, nil) + require.NoError(t, err) + require.True(t, output.syncCalled) +} + +type brokenEncoder struct{ zapcore.Encoder } + +func (b *brokenEncoder) EncodeEntry(zapcore.Entry, []zapcore.Field) (*buffer.Buffer, error) { + return nil, errors.New("broken encoder") +} + +func TestCoreWriteEncodeFail(t *testing.T) { + output := &sw{} + core := &flogging.Core{ + Encoders: map[flogging.Encoding]zapcore.Encoder{ + flogging.CONSOLE: &brokenEncoder{}, + }, + Selector: output, + Output: output, + } + + entry := zapcore.Entry{ + Level: zapcore.DebugLevel, + Message: "no bugs for me", + } + err := core.Write(entry, nil) + require.EqualError(t, err, "broken encoder") +} + +func TestCoreSync(t *testing.T) { + syncWriter := &sw{} + core := &flogging.Core{ + Output: syncWriter, + } + + err := core.Sync() + require.NoError(t, err) + require.True(t, syncWriter.syncCalled) + + syncWriter.syncErr = errors.New("bummer") + err = core.Sync() + require.EqualError(t, err, "bummer") +} + +func TestObserverCheck(t *testing.T) { + observer := &mock.Observer{} + entry := zapcore.Entry{ + Level: zapcore.DebugLevel, + Message: "message", + } + checkedEntry := &zapcore.CheckedEntry{} + + levels := &flogging.LoggerLevels{} + err := levels.ActivateSpec("debug") + require.NoError(t, err) + core := &flogging.Core{ + LevelEnabler: zap.LevelEnablerFunc(func(l zapcore.Level) bool { return true }), + Levels: levels, + Observer: observer, + } + + ce := core.Check(entry, checkedEntry) + require.Exactly(t, ce, checkedEntry) + + require.Equal(t, 1, observer.CheckCallCount()) + observedEntry, observedCE := observer.CheckArgsForCall(0) + require.Equal(t, entry, observedEntry) + require.Equal(t, ce, observedCE) +} + +func TestObserverWriteEntry(t *testing.T) { + observer := &mock.Observer{} + entry := zapcore.Entry{ + Level: zapcore.DebugLevel, + Message: "message", + } + fields := []zapcore.Field{ + {Key: "key1", Type: zapcore.SkipType}, + {Key: "key2", Type: zapcore.SkipType}, + } + + levels := &flogging.LoggerLevels{} + err := levels.ActivateSpec("debug") + require.NoError(t, err) + selector := &sw{} + output := &sw{} + core := &flogging.Core{ + LevelEnabler: zap.LevelEnablerFunc(func(l zapcore.Level) bool { return true }), + Levels: levels, + Selector: selector, + Encoders: map[flogging.Encoding]zapcore.Encoder{ + flogging.CONSOLE: zapcore.NewConsoleEncoder(zapcore.EncoderConfig{}), + }, + Output: output, + Observer: observer, + } + + err = core.Write(entry, fields) + require.NoError(t, err) + + require.Equal(t, 1, observer.WriteEntryCallCount()) + observedEntry, observedFields := observer.WriteEntryArgsForCall(0) + require.Equal(t, entry, observedEntry) + require.Equal(t, fields, observedFields) +} diff --git a/v2/common/flogging/fabenc/color.go b/v2/common/flogging/fabenc/color.go new file mode 100644 index 0000000..240d0f7 --- /dev/null +++ b/v2/common/flogging/fabenc/color.go @@ -0,0 +1,39 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc + +import ( + "fmt" +) + +type Color uint8 + +const ColorNone Color = 0 + +const ( + ColorBlack Color = iota + 30 + ColorRed + ColorGreen + ColorYellow + ColorBlue + ColorMagenta + ColorCyan + ColorWhite +) + +func (c Color) Normal() string { + return fmt.Sprintf("\x1b[%dm", c) +} + +func (c Color) Bold() string { + if c == ColorNone { + return c.Normal() + } + return fmt.Sprintf("\x1b[%d;1m", c) +} + +func ResetColor() string { return ColorNone.Normal() } diff --git a/v2/common/flogging/fabenc/color_test.go b/v2/common/flogging/fabenc/color_test.go new file mode 100644 index 0000000..a6843d0 --- /dev/null +++ b/v2/common/flogging/fabenc/color_test.go @@ -0,0 +1,40 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc_test + +import ( + "testing" + + "github.com/IBM/idemix/v2/common/flogging/fabenc" + "github.com/stretchr/testify/require" +) + +func TestReset(t *testing.T) { + require.Equal(t, fabenc.ResetColor(), "\x1b[0m") +} + +func TestNormalColors(t *testing.T) { + require.Equal(t, fabenc.ColorBlack.Normal(), "\x1b[30m") + require.Equal(t, fabenc.ColorRed.Normal(), "\x1b[31m") + require.Equal(t, fabenc.ColorGreen.Normal(), "\x1b[32m") + require.Equal(t, fabenc.ColorYellow.Normal(), "\x1b[33m") + require.Equal(t, fabenc.ColorBlue.Normal(), "\x1b[34m") + require.Equal(t, fabenc.ColorMagenta.Normal(), "\x1b[35m") + require.Equal(t, fabenc.ColorCyan.Normal(), "\x1b[36m") + require.Equal(t, fabenc.ColorWhite.Normal(), "\x1b[37m") +} + +func TestBoldColors(t *testing.T) { + require.Equal(t, fabenc.ColorBlack.Bold(), "\x1b[30;1m") + require.Equal(t, fabenc.ColorRed.Bold(), "\x1b[31;1m") + require.Equal(t, fabenc.ColorGreen.Bold(), "\x1b[32;1m") + require.Equal(t, fabenc.ColorYellow.Bold(), "\x1b[33;1m") + require.Equal(t, fabenc.ColorBlue.Bold(), "\x1b[34;1m") + require.Equal(t, fabenc.ColorMagenta.Bold(), "\x1b[35;1m") + require.Equal(t, fabenc.ColorCyan.Bold(), "\x1b[36;1m") + require.Equal(t, fabenc.ColorWhite.Bold(), "\x1b[37;1m") +} diff --git a/v2/common/flogging/fabenc/encoder.go b/v2/common/flogging/fabenc/encoder.go new file mode 100644 index 0000000..4d19a09 --- /dev/null +++ b/v2/common/flogging/fabenc/encoder.go @@ -0,0 +1,80 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc + +import ( + "io" + "time" + + zaplogfmt "github.com/sykesm/zap-logfmt" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// A FormatEncoder is a zapcore.Encoder that formats log records according to a +// go-logging based format specifier. +type FormatEncoder struct { + zapcore.Encoder + formatters []Formatter + pool buffer.Pool +} + +// A Formatter is used to format and write data from a zap log entry. +type Formatter interface { + Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) +} + +func NewFormatEncoder(formatters ...Formatter) *FormatEncoder { + return &FormatEncoder{ + Encoder: zaplogfmt.NewEncoder(zapcore.EncoderConfig{ + MessageKey: "", // disable + LevelKey: "", // disable + TimeKey: "", // disable + NameKey: "", // disable + CallerKey: "", // disable + StacktraceKey: "", // disable + LineEnding: "\n", + EncodeDuration: zapcore.StringDurationEncoder, + EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02T15:04:05.999Z07:00")) + }, + }), + formatters: formatters, + pool: buffer.NewPool(), + } +} + +// Clone creates a new instance of this encoder with the same configuration. +func (f *FormatEncoder) Clone() zapcore.Encoder { + return &FormatEncoder{ + Encoder: f.Encoder.Clone(), + formatters: f.formatters, + pool: f.pool, + } +} + +// EncodeEntry formats a zap log record. The structured fields are formatted by a +// zapcore.ConsoleEncoder and are appended as JSON to the end of the formatted entry. +// All entries are terminated by a newline. +func (f *FormatEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + line := f.pool.Get() + for _, f := range f.formatters { + f.Format(line, entry, fields) + } + + encodedFields, err := f.Encoder.EncodeEntry(entry, fields) + if err != nil { + return nil, err + } + if line.Len() > 0 && encodedFields.Len() != 1 { + line.AppendString(" ") + } + line.AppendString(encodedFields.String()) + encodedFields.Free() + + return line, nil +} diff --git a/v2/common/flogging/fabenc/encoder_test.go b/v2/common/flogging/fabenc/encoder_test.go new file mode 100644 index 0000000..d5f94aa --- /dev/null +++ b/v2/common/flogging/fabenc/encoder_test.go @@ -0,0 +1,84 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc_test + +import ( + "errors" + "fmt" + "runtime" + "testing" + "time" + + "github.com/IBM/idemix/v2/common/flogging/fabenc" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +func TestEncodeEntry(t *testing.T) { + startTime := time.Now() + var tests = []struct { + name string + spec string + fields []zapcore.Field + expected string + }{ + {name: "empty spec and nil fields", spec: "", fields: nil, expected: "\n"}, + {name: "empty spec with fields", spec: "", fields: []zapcore.Field{zap.String("key", "value")}, expected: "key=value\n"}, + {name: "simple spec and nil fields", spec: "simple-string", expected: "simple-string\n"}, + {name: "simple spec and empty fields", spec: "simple-string", fields: []zapcore.Field{}, expected: "simple-string\n"}, + {name: "simple spec with fields", spec: "simple-string", fields: []zapcore.Field{zap.String("key", "value")}, expected: "simple-string key=value\n"}, + {name: "duration", spec: "", fields: []zapcore.Field{zap.Duration("duration", time.Second)}, expected: "duration=1s\n"}, + {name: "time", spec: "", fields: []zapcore.Field{zap.Time("time", startTime)}, expected: fmt.Sprintf("time=%s\n", startTime.Format("2006-01-02T15:04:05.999Z07:00"))}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + formatters, err := fabenc.ParseFormat(tc.spec) + require.NoError(t, err) + + enc := fabenc.NewFormatEncoder(formatters...) + + pc, file, l, ok := runtime.Caller(0) + line, err := enc.EncodeEntry( + zapcore.Entry{ + // The entry information should be completely omitted + Level: zapcore.InfoLevel, + Time: startTime, + LoggerName: "logger-name", + Message: "message", + Caller: zapcore.NewEntryCaller(pc, file, l, ok), + Stack: "stack", + }, + tc.fields, + ) + require.NoError(t, err) + require.Equal(t, tc.expected, line.String()) + }) + } +} + +type brokenEncoder struct{ zapcore.Encoder } + +func (b *brokenEncoder) EncodeEntry(zapcore.Entry, []zapcore.Field) (*buffer.Buffer, error) { + return nil, errors.New("broken encoder") +} + +func TestEncodeFieldsFailed(t *testing.T) { + enc := fabenc.NewFormatEncoder() + enc.Encoder = &brokenEncoder{} + + _, err := enc.EncodeEntry(zapcore.Entry{}, nil) + require.EqualError(t, err, "broken encoder") +} + +func TestFormatEncoderClone(t *testing.T) { + enc := fabenc.NewFormatEncoder() + cloned := enc.Clone() + require.Equal(t, enc, cloned) +} diff --git a/v2/common/flogging/fabenc/formatter.go b/v2/common/flogging/fabenc/formatter.go new file mode 100644 index 0000000..b2101e5 --- /dev/null +++ b/v2/common/flogging/fabenc/formatter.go @@ -0,0 +1,294 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc + +import ( + "fmt" + "io" + "regexp" + "runtime" + "strings" + "sync" + "sync/atomic" + + "go.uber.org/zap/zapcore" +) + +// formatRegexp is broken into three groups: +// 1. the format verb +// 2. an optional colon that is ungrouped with '?:' +// 3. an optional, non-greedy format directive +// +// The grouping simplifies the verb proccssing during spec parsing. +var formatRegexp = regexp.MustCompile(`%{(color|id|level|message|module|shortfunc|time)(?::(.*?))?}`) + +// ParseFormat parses a log format spec and returns a slice of formatters +// that should be iterated over to build a formatted log record. +// +// The op-loggng specifiers supported by this formatter are: +// - %{color} - level specific SGR color escape or SGR reset +// - %{id} - a unique log sequence number +// - %{level} - the log level of the entry +// - %{message} - the log message +// - %{module} - the zap logger name +// - %{shortfunc} - the name of the function creating the log record +// - %{time} - the time the log entry was created +// +// Specifiers may include an optional format verb: +// - color: reset|bold +// - id: a fmt style numeric formatter without the leading % +// - level: a fmt style string formatter without the leading % +// - message: a fmt style string formatter without the leading % +// - module: a fmt style string formatter without the leading % +func ParseFormat(spec string) ([]Formatter, error) { + cursor := 0 + formatters := []Formatter{} + + // iterate over the regex groups and convert to formatters + matches := formatRegexp.FindAllStringSubmatchIndex(spec, -1) + for _, m := range matches { + start, end := m[0], m[1] + verbStart, verbEnd := m[2], m[3] + formatStart, formatEnd := m[4], m[5] + + if start > cursor { + formatters = append(formatters, StringFormatter{Value: spec[cursor:start]}) + } + + var format string + if formatStart >= 0 { + format = spec[formatStart:formatEnd] + } + + formatter, err := NewFormatter(spec[verbStart:verbEnd], format) + if err != nil { + return nil, err + } + + formatters = append(formatters, formatter) + cursor = end + } + + // handle any trailing suffix + if cursor != len(spec) { + formatters = append(formatters, StringFormatter{Value: spec[cursor:]}) + } + + return formatters, nil +} + +// A MultiFormatter presents multiple formatters as a single Formatter. It can +// be used to change the set of formatters associated with an encoder at +// runtime. +type MultiFormatter struct { + mutex sync.RWMutex + formatters []Formatter +} + +// NewMultiFormatter creates a new MultiFormatter that delegates to the +// provided formatters. The formatters are used in the order they are +// presented. +func NewMultiFormatter(formatters ...Formatter) *MultiFormatter { + return &MultiFormatter{ + formatters: formatters, + } +} + +// Format iterates over its delegates to format a log record to the provided +// buffer. +func (m *MultiFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + m.mutex.RLock() + for i := range m.formatters { + m.formatters[i].Format(w, entry, fields) + } + m.mutex.RUnlock() +} + +// SetFormatters replaces the delegate formatters. +func (m *MultiFormatter) SetFormatters(formatters []Formatter) { + m.mutex.Lock() + m.formatters = formatters + m.mutex.Unlock() +} + +// A StringFormatter formats a fixed string. +type StringFormatter struct{ Value string } + +// Format writes the formatter's fixed string to provided writer. +func (s StringFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprintf(w, "%s", s.Value) +} + +// NewFormatter creates the formatter for the provided verb. When a format is +// not provided, the default format for the verb is used. +func NewFormatter(verb, format string) (Formatter, error) { + switch verb { + case "color": + return newColorFormatter(format) + case "id": + return newSequenceFormatter(format), nil + case "level": + return newLevelFormatter(format), nil + case "message": + return newMessageFormatter(format), nil + case "module": + return newModuleFormatter(format), nil + case "shortfunc": + return newShortFuncFormatter(format), nil + case "time": + return newTimeFormatter(format), nil + default: + return nil, fmt.Errorf("unknown verb: %s", verb) + } +} + +// A ColorFormatter formats an SGR color code. +type ColorFormatter struct { + Bold bool // set the bold attribute + Reset bool // reset colors and attributes +} + +func newColorFormatter(f string) (ColorFormatter, error) { + switch f { + case "bold": + return ColorFormatter{Bold: true}, nil + case "reset": + return ColorFormatter{Reset: true}, nil + case "": + return ColorFormatter{}, nil + default: + return ColorFormatter{}, fmt.Errorf("invalid color option: %s", f) + } +} + +// LevelColor returns the Color associated with a specific zap logging level. +func (c ColorFormatter) LevelColor(l zapcore.Level) Color { + switch l { + case zapcore.DebugLevel: + return ColorCyan + case zapcore.InfoLevel: + return ColorBlue + case zapcore.WarnLevel: + return ColorYellow + case zapcore.ErrorLevel: + return ColorRed + case zapcore.DPanicLevel, zapcore.PanicLevel: + return ColorMagenta + case zapcore.FatalLevel: + return ColorMagenta + default: + return ColorNone + } +} + +// Format writes the SGR color code to the provided writer. +func (c ColorFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + switch { + case c.Reset: + fmt.Fprint(w, ResetColor()) + case c.Bold: + fmt.Fprint(w, c.LevelColor(entry.Level).Bold()) + default: + fmt.Fprint(w, c.LevelColor(entry.Level).Normal()) + } +} + +// LevelFormatter formats a log level. +type LevelFormatter struct{ FormatVerb string } + +func newLevelFormatter(f string) LevelFormatter { + return LevelFormatter{FormatVerb: "%" + stringOrDefault(f, "s")} +} + +// Format writes the logging level to the provided writer. +func (l LevelFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprintf(w, l.FormatVerb, entry.Level.CapitalString()) +} + +// MessageFormatter formats a log message. +type MessageFormatter struct{ FormatVerb string } + +func newMessageFormatter(f string) MessageFormatter { + return MessageFormatter{FormatVerb: "%" + stringOrDefault(f, "s")} +} + +// Format writes the log entry message to the provided writer. +func (m MessageFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprintf(w, m.FormatVerb, strings.TrimRight(entry.Message, "\n")) +} + +// ModuleFormatter formats the zap logger name. +type ModuleFormatter struct{ FormatVerb string } + +func newModuleFormatter(f string) ModuleFormatter { + return ModuleFormatter{FormatVerb: "%" + stringOrDefault(f, "s")} +} + +// Format writes the zap logger name to the specified writer. +func (m ModuleFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprintf(w, m.FormatVerb, entry.LoggerName) +} + +// sequence maintains the global sequence number shared by all SequeneFormatter +// instances. +var sequence uint64 + +// SetSequence explicitly sets the global sequence number. +func SetSequence(s uint64) { atomic.StoreUint64(&sequence, s) } + +// SequenceFormatter formats a global sequence number. +type SequenceFormatter struct{ FormatVerb string } + +func newSequenceFormatter(f string) SequenceFormatter { + return SequenceFormatter{FormatVerb: "%" + stringOrDefault(f, "d")} +} + +// SequenceFormatter increments a global sequence number and writes it to the +// provided writer. +func (s SequenceFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprintf(w, s.FormatVerb, atomic.AddUint64(&sequence, 1)) +} + +// ShortFuncFormatter formats the name of the function creating the log record. +type ShortFuncFormatter struct{ FormatVerb string } + +func newShortFuncFormatter(f string) ShortFuncFormatter { + return ShortFuncFormatter{FormatVerb: "%" + stringOrDefault(f, "s")} +} + +// Format writes the calling function name to the provided writer. The name is obtained from +// the runtime and the package and line numbers are discarded. +func (s ShortFuncFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + f := runtime.FuncForPC(entry.Caller.PC) + if f == nil { + fmt.Fprintf(w, s.FormatVerb, "(unknown)") + return + } + + fname := f.Name() + funcIdx := strings.LastIndex(fname, ".") + fmt.Fprintf(w, s.FormatVerb, fname[funcIdx+1:]) +} + +// TimeFormatter formats the time from the zap log entry. +type TimeFormatter struct{ Layout string } + +func newTimeFormatter(f string) TimeFormatter { + return TimeFormatter{Layout: stringOrDefault(f, "2006-01-02T15:04:05.999Z07:00")} +} + +// Format writes the log record time stamp to the provided writer. +func (t TimeFormatter) Format(w io.Writer, entry zapcore.Entry, fields []zapcore.Field) { + fmt.Fprint(w, entry.Time.Format(t.Layout)) +} + +func stringOrDefault(str, dflt string) string { + if str != "" { + return str + } + return dflt +} diff --git a/v2/common/flogging/fabenc/formatter_test.go b/v2/common/flogging/fabenc/formatter_test.go new file mode 100644 index 0000000..3800ab4 --- /dev/null +++ b/v2/common/flogging/fabenc/formatter_test.go @@ -0,0 +1,317 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabenc_test + +import ( + "bytes" + "fmt" + "runtime" + "strconv" + "sync" + "testing" + "time" + + "github.com/IBM/idemix/v2/common/flogging/fabenc" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func TestParseFormat(t *testing.T) { + var tests = []struct { + desc string + spec string + formatters []fabenc.Formatter + }{ + { + desc: "empty spec", + spec: "", + formatters: []fabenc.Formatter{}, + }, + { + desc: "simple verb", + spec: "%{color}", + formatters: []fabenc.Formatter{ + fabenc.ColorFormatter{}, + }, + }, + { + desc: "with prefix", + spec: "prefix %{color}", + formatters: []fabenc.Formatter{ + fabenc.StringFormatter{Value: "prefix "}, + fabenc.ColorFormatter{}, + }, + }, + { + desc: "with suffix", + spec: "%{color} suffix", + formatters: []fabenc.Formatter{ + fabenc.ColorFormatter{}, + fabenc.StringFormatter{Value: " suffix"}}, + }, + { + desc: "with prefix and suffix", + spec: "prefix %{color} suffix", + formatters: []fabenc.Formatter{ + fabenc.StringFormatter{Value: "prefix "}, + fabenc.ColorFormatter{}, + fabenc.StringFormatter{Value: " suffix"}, + }, + }, + { + desc: "with format", + spec: "%{level:.4s} suffix", + formatters: []fabenc.Formatter{ + fabenc.LevelFormatter{FormatVerb: "%.4s"}, + fabenc.StringFormatter{Value: " suffix"}, + }, + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf(tc.desc), func(t *testing.T) { + formatters, err := fabenc.ParseFormat(tc.spec) + require.NoError(t, err) + require.Equal(t, tc.formatters, formatters) + }) + } +} + +func TestParseFormatError(t *testing.T) { + _, err := fabenc.ParseFormat("%{color:bad}") + require.EqualError(t, err, "invalid color option: bad") +} + +func TestNewFormatter(t *testing.T) { + var tests = []struct { + verb string + format string + formatter fabenc.Formatter + errorMsg string + }{ + {verb: "color", format: "", formatter: fabenc.ColorFormatter{}}, + {verb: "color", format: "bold", formatter: fabenc.ColorFormatter{Bold: true}}, + {verb: "color", format: "reset", formatter: fabenc.ColorFormatter{Reset: true}}, + {verb: "color", format: "unknown", errorMsg: "invalid color option: unknown"}, + {verb: "id", format: "", formatter: fabenc.SequenceFormatter{FormatVerb: "%d"}}, + {verb: "id", format: "04x", formatter: fabenc.SequenceFormatter{FormatVerb: "%04x"}}, + {verb: "level", format: "", formatter: fabenc.LevelFormatter{FormatVerb: "%s"}}, + {verb: "level", format: ".4s", formatter: fabenc.LevelFormatter{FormatVerb: "%.4s"}}, + {verb: "message", format: "", formatter: fabenc.MessageFormatter{FormatVerb: "%s"}}, + {verb: "message", format: "#30s", formatter: fabenc.MessageFormatter{FormatVerb: "%#30s"}}, + {verb: "module", format: "", formatter: fabenc.ModuleFormatter{FormatVerb: "%s"}}, + {verb: "module", format: "ok", formatter: fabenc.ModuleFormatter{FormatVerb: "%ok"}}, + {verb: "shortfunc", format: "", formatter: fabenc.ShortFuncFormatter{FormatVerb: "%s"}}, + {verb: "shortfunc", format: "U", formatter: fabenc.ShortFuncFormatter{FormatVerb: "%U"}}, + {verb: "time", format: "", formatter: fabenc.TimeFormatter{Layout: "2006-01-02T15:04:05.999Z07:00"}}, + {verb: "time", format: "04:05.999999Z05:00", formatter: fabenc.TimeFormatter{Layout: "04:05.999999Z05:00"}}, + {verb: "unknown", format: "", errorMsg: "unknown verb: unknown"}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + f, err := fabenc.NewFormatter(tc.verb, tc.format) + if tc.errorMsg == "" { + require.NoError(t, err) + require.Equal(t, tc.formatter, f) + } else { + require.EqualError(t, err, tc.errorMsg) + } + }) + } +} + +func TestColorFormatter(t *testing.T) { + var tests = []struct { + f fabenc.ColorFormatter + level zapcore.Level + formatted string + }{ + {f: fabenc.ColorFormatter{Reset: true}, level: zapcore.DebugLevel, formatted: fabenc.ResetColor()}, + {f: fabenc.ColorFormatter{}, level: zapcore.DebugLevel, formatted: fabenc.ColorCyan.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.DebugLevel, formatted: fabenc.ColorCyan.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.InfoLevel, formatted: fabenc.ColorBlue.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.InfoLevel, formatted: fabenc.ColorBlue.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.WarnLevel, formatted: fabenc.ColorYellow.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.WarnLevel, formatted: fabenc.ColorYellow.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.ErrorLevel, formatted: fabenc.ColorRed.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.ErrorLevel, formatted: fabenc.ColorRed.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.DPanicLevel, formatted: fabenc.ColorMagenta.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.DPanicLevel, formatted: fabenc.ColorMagenta.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.PanicLevel, formatted: fabenc.ColorMagenta.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.PanicLevel, formatted: fabenc.ColorMagenta.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.FatalLevel, formatted: fabenc.ColorMagenta.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.FatalLevel, formatted: fabenc.ColorMagenta.Bold()}, + {f: fabenc.ColorFormatter{}, level: zapcore.Level(99), formatted: fabenc.ColorNone.Normal()}, + {f: fabenc.ColorFormatter{Bold: true}, level: zapcore.Level(99), formatted: fabenc.ColorNone.Normal()}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{Level: tc.level} + tc.f.Format(buf, entry, nil) + require.Equal(t, tc.formatted, buf.String()) + }) + } +} + +func TestLevelFormatter(t *testing.T) { + var tests = []struct { + level zapcore.Level + formatted string + }{ + {level: zapcore.DebugLevel, formatted: "DEBUG"}, + {level: zapcore.InfoLevel, formatted: "INFO"}, + {level: zapcore.WarnLevel, formatted: "WARN"}, + {level: zapcore.ErrorLevel, formatted: "ERROR"}, + {level: zapcore.DPanicLevel, formatted: "DPANIC"}, + {level: zapcore.PanicLevel, formatted: "PANIC"}, + {level: zapcore.FatalLevel, formatted: "FATAL"}, + {level: zapcore.Level(99), formatted: "LEVEL(99)"}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{Level: tc.level} + fabenc.LevelFormatter{FormatVerb: "%s"}.Format(buf, entry, nil) + require.Equal(t, tc.formatted, buf.String()) + }) + } +} + +func TestMessageFormatter(t *testing.T) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{Message: "some message text \n\n"} + f := fabenc.MessageFormatter{FormatVerb: "%s"} + f.Format(buf, entry, nil) + require.Equal(t, "some message text ", buf.String()) +} + +func TestModuleFormatter(t *testing.T) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{LoggerName: "logger/name"} + f := fabenc.ModuleFormatter{FormatVerb: "%s"} + f.Format(buf, entry, nil) + require.Equal(t, "logger/name", buf.String()) +} + +func TestSequenceFormatter(t *testing.T) { + mutex := &sync.Mutex{} + results := map[string]struct{}{} + + ready := &sync.WaitGroup{} + ready.Add(100) + + finished := &sync.WaitGroup{} + finished.Add(100) + + fabenc.SetSequence(0) + for i := 1; i <= 100; i++ { + go func(i int) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{Level: zapcore.DebugLevel} + f := fabenc.SequenceFormatter{FormatVerb: "%d"} + ready.Done() // setup complete + ready.Wait() // wait for all go routines to be ready + + f.Format(buf, entry, nil) // format concurrently + + mutex.Lock() + results[buf.String()] = struct{}{} + mutex.Unlock() + + finished.Done() + }(i) + } + + finished.Wait() + for i := 1; i <= 100; i++ { + require.Contains(t, results, strconv.Itoa(i)) + } +} + +func TestShortFuncFormatter(t *testing.T) { + callerpc, _, _, ok := runtime.Caller(0) + require.True(t, ok) + buf := &bytes.Buffer{} + entry := zapcore.Entry{Caller: zapcore.EntryCaller{PC: callerpc}} + fabenc.ShortFuncFormatter{FormatVerb: "%s"}.Format(buf, entry, nil) + require.Equal(t, "TestShortFuncFormatter", buf.String()) + + buf = &bytes.Buffer{} + entry = zapcore.Entry{Caller: zapcore.EntryCaller{PC: 0}} + fabenc.ShortFuncFormatter{FormatVerb: "%s"}.Format(buf, entry, nil) + require.Equal(t, "(unknown)", buf.String()) +} + +func TestTimeFormatter(t *testing.T) { + buf := &bytes.Buffer{} + entry := zapcore.Entry{Time: time.Date(1975, time.August, 15, 12, 0, 0, 333, time.UTC)} + f := fabenc.TimeFormatter{Layout: time.RFC3339Nano} + f.Format(buf, entry, nil) + require.Equal(t, "1975-08-15T12:00:00.000000333Z", buf.String()) +} + +func TestMultiFormatter(t *testing.T) { + entry := zapcore.Entry{ + Message: "message", + Level: zapcore.InfoLevel, + } + fields := []zapcore.Field{ + zap.String("name", "value"), + } + + var tests = []struct { + desc string + initial []fabenc.Formatter + update []fabenc.Formatter + expected string + }{ + { + desc: "no formatters", + initial: nil, + update: nil, + expected: "", + }, + { + desc: "initial formatters", + initial: []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}}, + update: nil, + expected: "string1", + }, + { + desc: "set to formatters", + initial: []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}}, + update: []fabenc.Formatter{ + fabenc.StringFormatter{Value: "string1"}, + fabenc.StringFormatter{Value: "-"}, + fabenc.StringFormatter{Value: "string2"}, + }, + expected: "string1-string2", + }, + { + desc: "set to empty", + initial: []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}}, + update: []fabenc.Formatter{}, + expected: "", + }, + } + + for _, tc := range tests { + mf := fabenc.NewMultiFormatter(tc.initial...) + if tc.update != nil { + mf.SetFormatters(tc.update) + } + + buf := &bytes.Buffer{} + mf.Format(buf, entry, fields) + require.Equal(t, tc.expected, buf.String()) + } +} diff --git a/v2/common/flogging/floggingtest/logger.go b/v2/common/flogging/floggingtest/logger.go new file mode 100644 index 0000000..67b0c48 --- /dev/null +++ b/v2/common/flogging/floggingtest/logger.go @@ -0,0 +1,242 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package floggingtest + +import ( + "bytes" + "regexp" + "strings" + "sync" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/IBM/idemix/v2/common/flogging/fabenc" + "github.com/onsi/gomega/gbytes" + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// DefaultFormat is a log encoding format that is mostly compatible with the default +// log format but excludes colorization and time. +const DefaultFormat = "[%{module}] %{shortfunc} -> %{level:.4s} %{id:04x} %{message}" + +type Recorder struct { + mutex sync.RWMutex + entries []string + messages []string + buffer *gbytes.Buffer +} + +func newRecorder() *Recorder { + return &Recorder{ + buffer: gbytes.NewBuffer(), + entries: []string{}, + messages: []string{}, + } +} + +func (r *Recorder) addEntry(e zapcore.Entry, line *buffer.Buffer) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.buffer.Write(line.Bytes()) + r.entries = append(r.entries, strings.TrimRight(line.String(), "\n")) + r.messages = append(r.messages, e.Message) +} + +func (r *Recorder) Reset() { + r.mutex.Lock() + defer r.mutex.Unlock() + r.buffer = gbytes.NewBuffer() + r.entries = []string{} + r.messages = []string{} +} + +func (r *Recorder) Buffer() *gbytes.Buffer { + r.mutex.RLock() + defer r.mutex.RUnlock() + return r.buffer +} + +func (r *Recorder) Entries() []string { + r.mutex.RLock() + defer r.mutex.RUnlock() + + entries := make([]string, len(r.entries), cap(r.entries)) + copy(entries, r.entries) + return entries +} + +func (r *Recorder) EntriesContaining(sub string) []string { + r.mutex.RLock() + defer r.mutex.RUnlock() + + matches := []string{} + for _, entry := range r.entries { + if strings.Contains(entry, sub) { + matches = append(matches, entry) + } + } + return matches +} + +func (r *Recorder) EntriesMatching(regex string) []string { + re := regexp.MustCompile(regex) + r.mutex.RLock() + defer r.mutex.RUnlock() + + matches := []string{} + for _, entry := range r.entries { + if re.MatchString(entry) { + matches = append(matches, entry) + } + } + return matches +} + +func (r *Recorder) Messages() []string { + r.mutex.RLock() + defer r.mutex.RUnlock() + + messages := make([]string, len(r.messages), cap(r.messages)) + copy(messages, r.messages) + return messages +} + +func (r *Recorder) MessagesContaining(sub string) []string { + r.mutex.RLock() + defer r.mutex.RUnlock() + + matches := []string{} + for _, msg := range r.messages { + if strings.Contains(msg, sub) { + matches = append(matches, msg) + } + } + return matches +} + +func (r *Recorder) MessagesMatching(regex string) []string { + re := regexp.MustCompile(regex) + r.mutex.RLock() + defer r.mutex.RUnlock() + + matches := []string{} + for _, msg := range r.messages { + if re.MatchString(msg) { + matches = append(matches, msg) + } + } + return matches +} + +type RecordingCore struct { + zapcore.LevelEnabler + encoder zapcore.Encoder + recorder *Recorder + writer zapcore.WriteSyncer +} + +func (r *RecordingCore) Write(e zapcore.Entry, fields []zapcore.Field) error { + buf, err := r.encoder.EncodeEntry(e, fields) + if err != nil { + return err + } + + r.writer.Write(buf.Bytes()) + r.recorder.addEntry(e, buf) + + buf.Free() + + return nil +} + +func (r *RecordingCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if r.Enabled(e.Level) { + ce = ce.AddCore(e, r) + } + if ce != nil && e.Level == zapcore.FatalLevel { + panic(e.Message) + } + return ce +} + +func (r *RecordingCore) With(fields []zapcore.Field) zapcore.Core { + clone := &RecordingCore{ + LevelEnabler: r.LevelEnabler, + encoder: r.encoder.Clone(), + recorder: r.recorder, + writer: r.writer, + } + + for _, f := range fields { + f.AddTo(clone.encoder) + } + + return clone +} + +func (r *RecordingCore) Sync() error { + return r.writer.Sync() +} + +type TestingWriter struct{ testing.TB } + +func (t *TestingWriter) Write(buf []byte) (int, error) { + t.Logf("%s", bytes.TrimRight(buf, "\n")) + return len(buf), nil +} + +func (t *TestingWriter) Sync() error { return nil } + +type Option func(r *RecordingCore, l *zap.Logger) *zap.Logger + +func Named(loggerName string) Option { + return func(r *RecordingCore, l *zap.Logger) *zap.Logger { + return l.Named(loggerName) + } +} + +func AtLevel(level zapcore.Level) Option { + return func(r *RecordingCore, l *zap.Logger) *zap.Logger { + r.LevelEnabler = zap.LevelEnablerFunc(func(l zapcore.Level) bool { + return level.Enabled(l) + }) + return l + } +} + +func NewTestLogger(tb testing.TB, options ...Option) (*flogging.FabricLogger, *Recorder) { + enabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { + return zapcore.DebugLevel.Enabled(l) + }) + + formatters, err := fabenc.ParseFormat(DefaultFormat) + if err != nil { + tb.Fatalf("failed to parse format %s: %s", DefaultFormat, err) + } + encoder := fabenc.NewFormatEncoder(formatters...) + if err != nil { + tb.Fatalf("failed to create format encoder: %s", err) + } + + recorder := newRecorder() + recordingCore := &RecordingCore{ + LevelEnabler: enabler, + encoder: encoder, + recorder: recorder, + writer: &TestingWriter{TB: tb}, + } + + zl := zap.New(recordingCore) + for _, o := range options { + zl = o(recordingCore, zl) + } + + return flogging.NewFabricLogger(zl, zap.AddCaller()), recorder +} diff --git a/v2/common/flogging/floggingtest/logger_test.go b/v2/common/flogging/floggingtest/logger_test.go new file mode 100644 index 0000000..8e288b5 --- /dev/null +++ b/v2/common/flogging/floggingtest/logger_test.go @@ -0,0 +1,92 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package floggingtest + +import ( + "testing" + + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "go.uber.org/zap/zapcore" +) + +func TestLoggerRecorder(t *testing.T) { + gt := NewGomegaWithT(t) + + tl, recorder := NewTestLogger(t, Named("test-logging"), AtLevel(zapcore.InfoLevel)) + tl.Error("this", "is", "an", "error") + + gt.Expect(recorder.Entries()).To(HaveLen(1)) + gt.Expect(recorder.Entries()).To(ConsistOf("[test-logging] TestLoggerRecorder -> ERRO 0001 this is an error")) + + gt.Expect(recorder.Messages()).To(HaveLen(1)) + gt.Expect(recorder.Messages()).To(ConsistOf("this is an error")) + + gt.Expect(string(recorder.Buffer().Contents())).To(Equal("[test-logging] TestLoggerRecorder -> ERRO 0001 this is an error\n")) + gt.Expect(recorder).NotTo(gbytes.Say("nothing good")) + gt.Expect(recorder).To(gbytes.Say(`\Q[test-logging] TestLoggerRecorder -> ERRO 0001 this is an error\E`)) +} + +func TestLoggerRecorderRegex(t *testing.T) { + gt := NewGomegaWithT(t) + + tl, recorder := NewTestLogger(t, Named("test-logging")) + tl.Debug("message one") + tl.Debug("message two") + tl.Debug("message three") + + gt.Expect(recorder.EntriesContaining("message")).To(HaveLen(3)) + gt.Expect(recorder.EntriesMatching("test-logging.*message t")).To(HaveLen(2)) + gt.Expect(recorder.MessagesContaining("message")).To(HaveLen(3)) + gt.Expect(recorder.MessagesMatching("^message t")).To(HaveLen(2)) + + gt.Expect(recorder.EntriesContaining("one")).To(HaveLen(1)) + gt.Expect(recorder.MessagesContaining("one")).To(HaveLen(1)) + + gt.Expect(recorder.EntriesContaining("two")).To(HaveLen(1)) + gt.Expect(recorder.MessagesContaining("two")).To(HaveLen(1)) + + gt.Expect(recorder.EntriesContaining("")).To(HaveLen(3)) + gt.Expect(recorder.MessagesContaining("")).To(HaveLen(3)) + + gt.Expect(recorder.EntriesContaining("mismatch")).To(HaveLen(0)) + gt.Expect(recorder.MessagesContaining("mismatch")).To(HaveLen(0)) +} + +func TestRecorderReset(t *testing.T) { + gt := NewGomegaWithT(t) + + tl, recorder := NewTestLogger(t, Named("test-logging")) + tl.Debug("message one") + tl.Debug("message two") + tl.Debug("message three") + + gt.Expect(recorder.Entries()).To(HaveLen(3)) + gt.Expect(recorder.Messages()).To(HaveLen(3)) + gt.Expect(recorder.Buffer().Contents()).NotTo(BeEmpty()) + + recorder.Reset() + gt.Expect(recorder.Entries()).To(HaveLen(0)) + gt.Expect(recorder.Messages()).To(HaveLen(0)) + gt.Expect(recorder.Buffer().Contents()).To(BeEmpty()) +} + +func TestFatalAsPanic(t *testing.T) { + gt := NewGomegaWithT(t) + + tl, _ := NewTestLogger(t) + gt.Expect(func() { tl.Fatal("this", "is", "an", "error") }).To(Panic()) +} + +func TestRecordingCoreWith(t *testing.T) { + gt := NewGomegaWithT(t) + logger, recorder := NewTestLogger(t) + logger = logger.With("key", "value") + + logger.Debug("message") + gt.Expect(recorder).To(gbytes.Say(`message key=value`)) +} diff --git a/v2/common/flogging/global.go b/v2/common/flogging/global.go new file mode 100644 index 0000000..7db1588 --- /dev/null +++ b/v2/common/flogging/global.go @@ -0,0 +1,84 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "io" + + "go.uber.org/zap/zapcore" + "google.golang.org/grpc/grpclog" +) + +const ( + defaultFormat = "%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}" + defaultLevel = zapcore.InfoLevel +) + +var Global *Logging + +func init() { + logging, err := New(Config{}) + if err != nil { + panic(err) + } + + Global = logging + grpcLogger := Global.ZapLogger("grpc") + grpclog.SetLogger(NewGRPCLogger(grpcLogger)) +} + +// Init initializes logging with the provided config. +func Init(config Config) { + err := Global.Apply(config) + if err != nil { + panic(err) + } +} + +// Reset sets logging to the defaults defined in this package. +// +// Used in tests and in the package init +func Reset() { + Global.Apply(Config{}) +} + +// LoggerLevel gets the current logging level for the logger with the +// provided name. +func LoggerLevel(loggerName string) string { + return Global.Level(loggerName).String() +} + +// MustGetLogger creates a logger with the specified name. If an invalid name +// is provided, the operation will panic. +func MustGetLogger(loggerName string) *FabricLogger { + return Global.Logger(loggerName) +} + +// ActivateSpec is used to activate a logging specification. +func ActivateSpec(spec string) { + err := Global.ActivateSpec(spec) + if err != nil { + panic(err) + } +} + +// DefaultLevel returns the default log level. +func DefaultLevel() string { + return defaultLevel.String() +} + +// SetWriter calls SetWriter returning the previous value +// of the writer. +func SetWriter(w io.Writer) io.Writer { + return Global.SetWriter(w) +} + +// SetObserver calls SetObserver returning the previous value +// of the observer. +func SetObserver(observer Observer) Observer { + return Global.SetObserver(observer) +} diff --git a/v2/common/flogging/global_test.go b/v2/common/flogging/global_test.go new file mode 100644 index 0000000..702b42c --- /dev/null +++ b/v2/common/flogging/global_test.go @@ -0,0 +1,162 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "bytes" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/IBM/idemix/v2/common/flogging/mock" + "github.com/stretchr/testify/require" +) + +func TestGlobalReset(t *testing.T) { + flogging.Reset() + err := flogging.Global.SetFormat("json") + require.NoError(t, err) + err = flogging.Global.ActivateSpec("logger=debug") + require.NoError(t, err) + + system, err := flogging.New(flogging.Config{}) + require.NoError(t, err) + require.NotEqual(t, flogging.Global.LoggerLevels, system.LoggerLevels) + require.NotEqual(t, flogging.Global.Encoding(), system.Encoding()) + + flogging.Reset() + require.Equal(t, flogging.Global.LoggerLevels, system.LoggerLevels) + require.Equal(t, flogging.Global.Encoding(), system.Encoding()) +} + +func TestGlobalInitConsole(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + buf := &bytes.Buffer{} + flogging.Init(flogging.Config{ + Format: "%{message}", + LogSpec: "DEBUG", + Writer: buf, + }) + + logger := flogging.MustGetLogger("testlogger") + logger.Debug("this is a message") + + require.Equal(t, "this is a message\n", buf.String()) +} + +func TestGlobalInitJSON(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + buf := &bytes.Buffer{} + flogging.Init(flogging.Config{ + Format: "json", + LogSpec: "DEBUG", + Writer: buf, + }) + + logger := flogging.MustGetLogger("testlogger") + logger.Debug("this is a message") + + require.Regexp(t, `{"level":"debug","ts":\d+.\d+,"name":"testlogger","caller":"flogging/global_test.go:\d+","msg":"this is a message"}\s+`, buf.String()) +} + +func TestGlobalInitLogfmt(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + buf := &bytes.Buffer{} + flogging.Init(flogging.Config{ + Format: "logfmt", + LogSpec: "DEBUG", + Writer: buf, + }) + + logger := flogging.MustGetLogger("testlogger") + logger.Debug("this is a message") + + require.Regexp(t, `^ts=\d+.\d+ level=debug name=testlogger caller=flogging/global_test.go:\d+ msg="this is a message"`, buf.String()) +} + +func TestGlobalInitPanic(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + require.Panics(t, func() { + flogging.Init(flogging.Config{ + Format: "%{color:evil}", + }) + }) +} + +func TestGlobalDefaultLevel(t *testing.T) { + flogging.Reset() + + require.Equal(t, "info", flogging.DefaultLevel()) +} + +func TestGlobalLoggerLevel(t *testing.T) { + flogging.Reset() + require.Equal(t, "info", flogging.LoggerLevel("some.logger")) +} + +func TestGlobalMustGetLogger(t *testing.T) { + flogging.Reset() + + l := flogging.MustGetLogger("logger-name") + require.NotNil(t, l) +} + +func TestFlogginInitPanic(t *testing.T) { + defer flogging.Reset() + + require.Panics(t, func() { + flogging.Init(flogging.Config{ + Format: "%{color:broken}", + }) + }) +} + +func TestActivateSpec(t *testing.T) { + defer flogging.Reset() + + flogging.ActivateSpec("fatal") + require.Equal(t, "fatal", flogging.Global.Spec()) +} + +func TestActivateSpecPanic(t *testing.T) { + defer flogging.Reset() + + require.Panics(t, func() { + flogging.ActivateSpec("busted") + }) +} + +func TestGlobalSetObserver(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + observer := &mock.Observer{} + + flogging.Global.SetObserver(observer) + o := flogging.Global.SetObserver(nil) + require.Exactly(t, observer, o) +} + +func TestGlobalSetWriter(t *testing.T) { + flogging.Reset() + defer flogging.Reset() + + w := &bytes.Buffer{} + + old := flogging.Global.SetWriter(w) + flogging.Global.SetWriter(old) + original := flogging.Global.SetWriter(nil) + + require.Exactly(t, old, original) +} diff --git a/v2/common/flogging/levels.go b/v2/common/flogging/levels.go new file mode 100644 index 0000000..dae518d --- /dev/null +++ b/v2/common/flogging/levels.go @@ -0,0 +1,68 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "fmt" + "math" + + "go.uber.org/zap/zapcore" +) + +const ( + // DisabledLevel represents a disabled log level. Logs at this level should + // never be emitted. + DisabledLevel = zapcore.Level(math.MinInt8) + + // PayloadLevel is used to log the extremely detailed message level debug + // information. + PayloadLevel = zapcore.Level(zapcore.DebugLevel - 1) +) + +// NameToLevel converts a level name to a zapcore.Level. If the level name is +// unknown, zapcore.InfoLevel is returned. +func NameToLevel(level string) zapcore.Level { + l, err := nameToLevel(level) + if err != nil { + return zapcore.InfoLevel + } + return l +} + +func nameToLevel(level string) (zapcore.Level, error) { + switch level { + case "PAYLOAD", "payload": + return PayloadLevel, nil + case "DEBUG", "debug": + return zapcore.DebugLevel, nil + case "INFO", "info": + return zapcore.InfoLevel, nil + case "WARNING", "WARN", "warning", "warn": + return zapcore.WarnLevel, nil + case "ERROR", "error": + return zapcore.ErrorLevel, nil + case "DPANIC", "dpanic": + return zapcore.DPanicLevel, nil + case "PANIC", "panic": + return zapcore.PanicLevel, nil + case "FATAL", "fatal": + return zapcore.FatalLevel, nil + + case "NOTICE", "notice": + return zapcore.InfoLevel, nil // future + case "CRITICAL", "critical": + return zapcore.ErrorLevel, nil // future + + default: + return DisabledLevel, fmt.Errorf("invalid log level: %s", level) + } +} + +func IsValidLevel(level string) bool { + _, err := nameToLevel(level) + return err == nil +} diff --git a/v2/common/flogging/levels_test.go b/v2/common/flogging/levels_test.go new file mode 100644 index 0000000..46ccac9 --- /dev/null +++ b/v2/common/flogging/levels_test.go @@ -0,0 +1,74 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +func TestNameToLevel(t *testing.T) { + var tests = []struct { + names []string + level zapcore.Level + }{ + {names: []string{"PAYLOAD", "payload"}, level: flogging.PayloadLevel}, + {names: []string{"DEBUG", "debug"}, level: zapcore.DebugLevel}, + {names: []string{"INFO", "info"}, level: zapcore.InfoLevel}, + {names: []string{"WARNING", "warning", "WARN", "warn"}, level: zapcore.WarnLevel}, + {names: []string{"ERROR", "error"}, level: zapcore.ErrorLevel}, + {names: []string{"DPANIC", "dpanic"}, level: zapcore.DPanicLevel}, + {names: []string{"PANIC", "panic"}, level: zapcore.PanicLevel}, + {names: []string{"FATAL", "fatal"}, level: zapcore.FatalLevel}, + {names: []string{"NOTICE", "notice"}, level: zapcore.InfoLevel}, + {names: []string{"CRITICAL", "critical"}, level: zapcore.ErrorLevel}, + {names: []string{"unexpected", "invalid"}, level: zapcore.InfoLevel}, + } + + for _, tc := range tests { + for _, name := range tc.names { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.level, flogging.NameToLevel(name)) + }) + } + } +} + +func TestIsValidLevel(t *testing.T) { + validNames := []string{ + "PAYLOAD", "payload", + "DEBUG", "debug", + "INFO", "info", + "WARNING", "warning", + "WARN", "warn", + "ERROR", "error", + "DPANIC", "dpanic", + "PANIC", "panic", + "FATAL", "fatal", + "NOTICE", "notice", + "CRITICAL", "critical", + } + for _, name := range validNames { + t.Run(name, func(t *testing.T) { + require.True(t, flogging.IsValidLevel(name)) + }) + } + + invalidNames := []string{ + "george", "bob", + "warnings", "inf", + "DISABLED", "disabled", // can only be used programmatically + } + for _, name := range invalidNames { + t.Run(name, func(t *testing.T) { + require.False(t, flogging.IsValidLevel(name)) + }) + } +} diff --git a/v2/common/flogging/loggerlevels.go b/v2/common/flogging/loggerlevels.go new file mode 100644 index 0000000..d306e62 --- /dev/null +++ b/v2/common/flogging/loggerlevels.go @@ -0,0 +1,175 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "fmt" + "regexp" + "sort" + "strings" + "sync" + + "github.com/pkg/errors" + "go.uber.org/zap/zapcore" +) + +// LoggerLevels tracks the logging level of named loggers. +type LoggerLevels struct { + mutex sync.RWMutex + levelCache map[string]zapcore.Level + specs map[string]zapcore.Level + defaultLevel zapcore.Level + minLevel zapcore.Level +} + +// DefaultLevel returns the default logging level for loggers that do not have +// an explicit level set. +func (l *LoggerLevels) DefaultLevel() zapcore.Level { + l.mutex.RLock() + lvl := l.defaultLevel + l.mutex.RUnlock() + return lvl +} + +// ActivateSpec is used to modify logging levels. +// +// The logging specification has the following form: +// +// [[,...]=][:[[,...]=]...] +func (l *LoggerLevels) ActivateSpec(spec string) error { + l.mutex.Lock() + defer l.mutex.Unlock() + + defaultLevel := zapcore.InfoLevel + specs := map[string]zapcore.Level{} + for _, field := range strings.Split(spec, ":") { + split := strings.Split(field, "=") + switch len(split) { + case 1: // level + if field != "" && !IsValidLevel(field) { + return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) + } + defaultLevel = NameToLevel(field) + + case 2: // [,...]= + if split[0] == "" { + return errors.Errorf("invalid logging specification '%s': no logger specified in segment '%s'", spec, field) + } + if field != "" && !IsValidLevel(split[1]) { + return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) + } + + level := NameToLevel(split[1]) + loggers := strings.Split(split[0], ",") + for _, logger := range loggers { + // check if the logger name in the spec is valid. The + // trailing period is trimmed as logger names in specs + // ending with a period signifies that this part of the + // spec refers to the exact logger name (i.e. is not a prefix) + if !isValidLoggerName(strings.TrimSuffix(logger, ".")) { + return errors.Errorf("invalid logging specification '%s': bad logger name '%s'", spec, logger) + } + specs[logger] = level + } + + default: + return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) + } + } + + minLevel := defaultLevel + for _, lvl := range specs { + if lvl < minLevel { + minLevel = lvl + } + } + + l.minLevel = minLevel + l.defaultLevel = defaultLevel + l.specs = specs + l.levelCache = map[string]zapcore.Level{} + + return nil +} + +// logggerNameRegexp defines the valid logger names +var loggerNameRegexp = regexp.MustCompile(`^[[:alnum:]_#:-]+(\.[[:alnum:]_#:-]+)*$`) + +// isValidLoggerName checks whether a logger name contains only valid +// characters. Names that begin/end with periods or contain special +// characters (other than periods, underscores, pound signs, colons +// and dashes) are invalid. +func isValidLoggerName(loggerName string) bool { + return loggerNameRegexp.MatchString(loggerName) +} + +// Level returns the effective logging level for a logger. If a level has not +// been explicitly set for the logger, the default logging level will be +// returned. +func (l *LoggerLevels) Level(loggerName string) zapcore.Level { + if level, ok := l.cachedLevel(loggerName); ok { + return level + } + + l.mutex.Lock() + level := l.calculateLevel(loggerName) + l.levelCache[loggerName] = level + l.mutex.Unlock() + + return level +} + +// calculateLevel walks the logger name back to find the appropriate +// log level from the current spec. +func (l *LoggerLevels) calculateLevel(loggerName string) zapcore.Level { + candidate := loggerName + "." + for { + if lvl, ok := l.specs[candidate]; ok { + return lvl + } + + idx := strings.LastIndex(candidate, ".") + if idx <= 0 { + return l.defaultLevel + } + candidate = candidate[:idx] + } +} + +// cachedLevel attempts to retrieve the effective log level for a logger from the +// cache. If the logger is not found, ok will be false. +func (l *LoggerLevels) cachedLevel(loggerName string) (lvl zapcore.Level, ok bool) { + l.mutex.RLock() + level, ok := l.levelCache[loggerName] + l.mutex.RUnlock() + return level, ok +} + +// Spec returns a normalized version of the active logging spec. +func (l *LoggerLevels) Spec() string { + l.mutex.RLock() + defer l.mutex.RUnlock() + + var fields []string + for k, v := range l.specs { + fields = append(fields, fmt.Sprintf("%s=%s", k, v)) + } + + sort.Strings(fields) + fields = append(fields, l.defaultLevel.String()) + + return strings.Join(fields, ":") +} + +// Enabled function is an enabled check that evaluates the minimum active logging level. +// It serves as a fast check before the (relatively) expensive Check call in the core. +func (l *LoggerLevels) Enabled(lvl zapcore.Level) bool { + l.mutex.RLock() + enabled := l.minLevel.Enabled(lvl) + l.mutex.RUnlock() + return enabled +} diff --git a/v2/common/flogging/loggerlevels_test.go b/v2/common/flogging/loggerlevels_test.go new file mode 100644 index 0000000..b41a57d --- /dev/null +++ b/v2/common/flogging/loggerlevels_test.go @@ -0,0 +1,193 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "errors" + "strconv" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +func TestLoggerLevelsActivateSpec(t *testing.T) { + var tests = []struct { + spec string + expectedLevels map[string]zapcore.Level + expectedDefaultLevel zapcore.Level + }{ + { + spec: "DEBUG", + expectedLevels: map[string]zapcore.Level{}, + expectedDefaultLevel: zapcore.DebugLevel, + }, + { + spec: "INFO", + expectedLevels: map[string]zapcore.Level{}, + expectedDefaultLevel: zapcore.InfoLevel, + }, + { + spec: "logger=info:DEBUG", + expectedLevels: map[string]zapcore.Level{ + "logger": zapcore.InfoLevel, + "logger.a": zapcore.InfoLevel, + "logger.b": zapcore.InfoLevel, + "logger.a.b": zapcore.InfoLevel, + }, + expectedDefaultLevel: zapcore.DebugLevel, + }, + { + spec: "logger=info:logger.=error:DEBUG", + expectedLevels: map[string]zapcore.Level{ + "logger": zapcore.ErrorLevel, + "logger.a": zapcore.InfoLevel, + "logger.b": zapcore.InfoLevel, + "logger.a.b": zapcore.InfoLevel, + }, + expectedDefaultLevel: zapcore.DebugLevel, + }, + { + spec: "logger.a,logger.b=info:logger.c=WARN:DEBUG", + expectedLevels: map[string]zapcore.Level{ + "logger.a": zapcore.InfoLevel, + "logger.b": zapcore.InfoLevel, + "logger.c": zapcore.WarnLevel, + }, + expectedDefaultLevel: zapcore.DebugLevel, + }, + { + spec: "a.b=info:a,z=error:c.b=info:c.=warn:debug", + expectedLevels: map[string]zapcore.Level{ + "a": zapcore.ErrorLevel, + "z": zapcore.ErrorLevel, + "a.b": zapcore.InfoLevel, + "a.b.c": zapcore.InfoLevel, + "a.b.c.d": zapcore.InfoLevel, + "a.c": zapcore.ErrorLevel, + "c": zapcore.WarnLevel, + "c.a": zapcore.DebugLevel, + "c.b": zapcore.InfoLevel, + "d": zapcore.DebugLevel, + "ab.c": zapcore.DebugLevel, + "c.b.a": zapcore.InfoLevel, + "c.b.a.b": zapcore.InfoLevel, + }, + expectedDefaultLevel: zapcore.DebugLevel, + }, + { + spec: "info:warn", + expectedLevels: map[string]zapcore.Level{ + "a": zapcore.WarnLevel, + "a.b": zapcore.WarnLevel, + "b": zapcore.WarnLevel, + "c": zapcore.WarnLevel, + "d": zapcore.WarnLevel, + }, + expectedDefaultLevel: zapcore.WarnLevel, + }, + } + + for _, tc := range tests { + t.Run(tc.spec, func(t *testing.T) { + ll := &flogging.LoggerLevels{} + + err := ll.ActivateSpec(tc.spec) + require.NoError(t, err) + require.Equal(t, tc.expectedDefaultLevel, ll.DefaultLevel()) + for name, lvl := range tc.expectedLevels { + require.Equal(t, lvl, ll.Level(name)) + } + }) + } +} + +func TestLoggerLevelsActivateSpecErrors(t *testing.T) { + var tests = []struct { + spec string + err error + }{ + {spec: "=INFO:DEBUG", err: errors.New("invalid logging specification '=INFO:DEBUG': no logger specified in segment '=INFO'")}, + {spec: "=INFO=:DEBUG", err: errors.New("invalid logging specification '=INFO=:DEBUG': bad segment '=INFO='")}, + {spec: "bogus", err: errors.New("invalid logging specification 'bogus': bad segment 'bogus'")}, + {spec: "a.b=info:a=broken:c.b=info:c.=warn:debug", err: errors.New("invalid logging specification 'a.b=info:a=broken:c.b=info:c.=warn:debug': bad segment 'a=broken'")}, + {spec: "a*=info:debug", err: errors.New("invalid logging specification 'a*=info:debug': bad logger name 'a*'")}, + {spec: ".a=info:debug", err: errors.New("invalid logging specification '.a=info:debug': bad logger name '.a'")}, + } + for _, tc := range tests { + t.Run(tc.spec, func(t *testing.T) { + ll := &flogging.LoggerLevels{} + err := ll.ActivateSpec("fatal:a=warn") + require.Nil(t, err) + + err = ll.ActivateSpec(tc.spec) + require.EqualError(t, err, tc.err.Error()) + + require.Equal(t, zapcore.FatalLevel, ll.DefaultLevel(), "default should not change") + require.Equal(t, zapcore.WarnLevel, ll.Level("a.b"), "log levels should not change") + }) + } +} + +func TestSpec(t *testing.T) { + var tests = []struct { + input string + output string + }{ + {input: "", output: "info"}, + {input: "debug", output: "debug"}, + {input: "a.=info:warning", output: "a.=info:warn"}, + {input: "a-b=error", output: "a-b=error:info"}, + {input: "a#b=error", output: "a#b=error:info"}, + {input: "a_b=error", output: "a_b=error:info"}, + {input: "debug:a=info:b=warn", output: "a=info:b=warn:debug"}, + {input: "b=warn:a=error", output: "a=error:b=warn:info"}, + } + + for _, tc := range tests { + ll := &flogging.LoggerLevels{} + err := ll.ActivateSpec(tc.input) + require.NoError(t, err) + + require.Equal(t, tc.output, ll.Spec()) + } +} + +func TestEnabled(t *testing.T) { + var tests = []struct { + spec string + enabledAt zapcore.Level + }{ + {spec: "payload", enabledAt: flogging.PayloadLevel}, + {spec: "debug", enabledAt: zapcore.DebugLevel}, + {spec: "info", enabledAt: zapcore.InfoLevel}, + {spec: "warn", enabledAt: zapcore.WarnLevel}, + {spec: "panic", enabledAt: zapcore.PanicLevel}, + {spec: "fatal", enabledAt: zapcore.FatalLevel}, + {spec: "fatal:a=debug", enabledAt: zapcore.DebugLevel}, + {spec: "a=fatal:b=warn", enabledAt: zapcore.InfoLevel}, + {spec: "a=warn", enabledAt: zapcore.InfoLevel}, + {spec: "a=debug", enabledAt: zapcore.DebugLevel}, + } + + for i, tc := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ll := &flogging.LoggerLevels{} + err := ll.ActivateSpec(tc.spec) + require.NoError(t, err) + + for i := flogging.PayloadLevel; i <= zapcore.FatalLevel; i++ { + if tc.enabledAt <= i { + require.Truef(t, ll.Enabled(i), "expected level %s and spec %s to be enabled", zapcore.Level(i), tc.spec) + } else { + require.False(t, ll.Enabled(i), "expected level %s and spec %s to be disabled", zapcore.Level(i), tc.spec) + } + } + }) + } +} diff --git a/v2/common/flogging/logging.go b/v2/common/flogging/logging.go new file mode 100644 index 0000000..9b28587 --- /dev/null +++ b/v2/common/flogging/logging.go @@ -0,0 +1,250 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "fmt" + "io" + "os" + "sync" + + "github.com/IBM/idemix/v2/common/flogging/fabenc" + zaplogfmt "github.com/sykesm/zap-logfmt" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Config is used to provide dependencies to a Logging instance. +type Config struct { + // Format is the log record format specifier for the Logging instance. If the + // spec is the string "json", log records will be formatted as JSON. Any + // other string will be provided to the FormatEncoder. Please see + // fabenc.ParseFormat for details on the supported verbs. + // + // If Format is not provided, a default format that provides basic information will + // be used. + Format string + + // LogSpec determines the log levels that are enabled for the logging system. The + // spec must be in a format that can be processed by ActivateSpec. + // + // If LogSpec is not provided, loggers will be enabled at the INFO level. + LogSpec string + + // Writer is the sink for encoded and formatted log records. + // + // If a Writer is not provided, os.Stderr will be used as the log sink. + Writer io.Writer +} + +// Logging maintains the state associated with the fabric logging system. It is +// intended to bridge between the legacy logging infrastructure built around +// go-logging and the structured, level logging provided by zap. +type Logging struct { + *LoggerLevels + + mutex sync.RWMutex + encoding Encoding + encoderConfig zapcore.EncoderConfig + multiFormatter *fabenc.MultiFormatter + writer zapcore.WriteSyncer + observer Observer +} + +// New creates a new logging system and initializes it with the provided +// configuration. +func New(c Config) (*Logging, error) { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.NameKey = "name" + + l := &Logging{ + LoggerLevels: &LoggerLevels{ + defaultLevel: defaultLevel, + }, + encoderConfig: encoderConfig, + multiFormatter: fabenc.NewMultiFormatter(), + } + + err := l.Apply(c) + if err != nil { + return nil, err + } + return l, nil +} + +// Apply applies the provided configuration to the logging system. +func (l *Logging) Apply(c Config) error { + err := l.SetFormat(c.Format) + if err != nil { + return err + } + + if c.LogSpec == "" { + c.LogSpec = os.Getenv("FABRIC_LOGGING_SPEC") + } + if c.LogSpec == "" { + c.LogSpec = defaultLevel.String() + } + + err = l.LoggerLevels.ActivateSpec(c.LogSpec) + if err != nil { + return err + } + + if c.Writer == nil { + c.Writer = os.Stderr + } + l.SetWriter(c.Writer) + + return nil +} + +// SetFormat updates how log records are formatted and encoded. Log entries +// created after this method has completed will use the new format. +// +// An error is returned if the log format specification cannot be parsed. +func (l *Logging) SetFormat(format string) error { + l.mutex.Lock() + defer l.mutex.Unlock() + if format == "" { + format = defaultFormat + } + + if format == "json" { + l.encoding = JSON + return nil + } + + if format == "logfmt" { + l.encoding = LOGFMT + return nil + } + + formatters, err := fabenc.ParseFormat(format) + if err != nil { + return err + } + l.multiFormatter.SetFormatters(formatters) + l.encoding = CONSOLE + + return nil +} + +// SetWriter controls which writer formatted log records are written to. +// Writers, with the exception of an *os.File, need to be safe for concurrent +// use by multiple go routines. +func (l *Logging) SetWriter(w io.Writer) io.Writer { + var sw zapcore.WriteSyncer + switch t := w.(type) { + case *os.File: + sw = zapcore.Lock(t) + case zapcore.WriteSyncer: + sw = t + default: + sw = zapcore.AddSync(w) + } + + l.mutex.Lock() + ow := l.writer + l.writer = sw + l.mutex.Unlock() + + return ow +} + +// SetObserver is used to provide a log observer that will be called as log +// levels are checked or written.. Only a single observer is supported. +func (l *Logging) SetObserver(observer Observer) Observer { + l.mutex.Lock() + so := l.observer + l.observer = observer + l.mutex.Unlock() + + return so +} + +// Write satisfies the io.Write contract. It delegates to the writer argument +// of SetWriter or the Writer field of Config. The Core uses this when encoding +// log records. +func (l *Logging) Write(b []byte) (int, error) { + l.mutex.RLock() + w := l.writer + l.mutex.RUnlock() + + return w.Write(b) +} + +// Sync satisfies the zapcore.WriteSyncer interface. It is used by the Core to +// flush log records before terminating the process. +func (l *Logging) Sync() error { + l.mutex.RLock() + w := l.writer + l.mutex.RUnlock() + + return w.Sync() +} + +// Encoding satisfies the Encoding interface. It determines whether the JSON or +// CONSOLE encoder should be used by the Core when log records are written. +func (l *Logging) Encoding() Encoding { + l.mutex.RLock() + e := l.encoding + l.mutex.RUnlock() + return e +} + +// ZapLogger instantiates a new zap.Logger with the specified name. The name is +// used to determine which log levels are enabled. +func (l *Logging) ZapLogger(name string) *zap.Logger { + if !isValidLoggerName(name) { + panic(fmt.Sprintf("invalid logger name: %s", name)) + } + + l.mutex.RLock() + core := &Core{ + LevelEnabler: l.LoggerLevels, + Levels: l.LoggerLevels, + Encoders: map[Encoding]zapcore.Encoder{ + JSON: zapcore.NewJSONEncoder(l.encoderConfig), + CONSOLE: fabenc.NewFormatEncoder(l.multiFormatter), + LOGFMT: zaplogfmt.NewEncoder(l.encoderConfig), + }, + Selector: l, + Output: l, + Observer: l, + } + l.mutex.RUnlock() + + return NewZapLogger(core).Named(name) +} + +func (l *Logging) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) { + l.mutex.RLock() + observer := l.observer + l.mutex.RUnlock() + + if observer != nil { + observer.Check(e, ce) + } +} + +func (l *Logging) WriteEntry(e zapcore.Entry, fields []zapcore.Field) { + l.mutex.RLock() + observer := l.observer + l.mutex.RUnlock() + + if observer != nil { + observer.WriteEntry(e, fields) + } +} + +// Logger instantiates a new FabricLogger with the specified name. The name is +// used to determine which log levels are enabled. +func (l *Logging) Logger(name string) *FabricLogger { + zl := l.ZapLogger(name) + return NewFabricLogger(zl) +} diff --git a/v2/common/flogging/logging_test.go b/v2/common/flogging/logging_test.go new file mode 100644 index 0000000..f433d55 --- /dev/null +++ b/v2/common/flogging/logging_test.go @@ -0,0 +1,158 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "bytes" + "errors" + "fmt" + "os" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/IBM/idemix/v2/common/flogging/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +func TestNew(t *testing.T) { + logging, err := flogging.New(flogging.Config{}) + require.NoError(t, err) + require.Equal(t, zapcore.InfoLevel, logging.DefaultLevel()) + + _, err = flogging.New(flogging.Config{ + LogSpec: "::=borken=::", + }) + require.EqualError(t, err, "invalid logging specification '::=borken=::': bad segment '=borken='") +} + +func TestNewWithEnvironment(t *testing.T) { + oldSpec, set := os.LookupEnv("FABRIC_LOGGING_SPEC") + if set { + defer os.Setenv("FABRIC_LOGGING_SPEC", oldSpec) + } + + os.Setenv("FABRIC_LOGGING_SPEC", "fatal") + logging, err := flogging.New(flogging.Config{}) + require.NoError(t, err) + require.Equal(t, zapcore.FatalLevel, logging.DefaultLevel()) + + os.Unsetenv("FABRIC_LOGGING_SPEC") + logging, err = flogging.New(flogging.Config{}) + require.NoError(t, err) + require.Equal(t, zapcore.InfoLevel, logging.DefaultLevel()) +} + +//go:generate counterfeiter -o mock/write_syncer.go -fake-name WriteSyncer . writeSyncer +type writeSyncer interface { + zapcore.WriteSyncer +} + +func TestLoggingSetWriter(t *testing.T) { + ws := &mock.WriteSyncer{} + + w := &bytes.Buffer{} + logging, err := flogging.New(flogging.Config{ + Writer: w, + }) + require.NoError(t, err) + + old := logging.SetWriter(ws) + logging.SetWriter(w) + original := logging.SetWriter(ws) + + require.Exactly(t, old, original) + _, err = logging.Write([]byte("hello")) + require.NoError(t, err) + require.Equal(t, 1, ws.WriteCallCount()) + require.Equal(t, []byte("hello"), ws.WriteArgsForCall(0)) + + err = logging.Sync() + require.NoError(t, err) + + ws.SyncReturns(errors.New("welp")) + err = logging.Sync() + require.EqualError(t, err, "welp") +} + +func TestNamedLogger(t *testing.T) { + defer flogging.Reset() + buf := &bytes.Buffer{} + flogging.Global.SetWriter(buf) + + t.Run("logger and named (child) logger with different levels", func(t *testing.T) { + defer buf.Reset() + logger := flogging.MustGetLogger("eugene") + logger2 := logger.Named("george") + flogging.ActivateSpec("eugene=info:eugene.george=error") + + logger.Info("from eugene") + logger2.Info("from george") + require.Contains(t, buf.String(), "from eugene") + require.NotContains(t, buf.String(), "from george") + }) + + t.Run("named logger where parent logger isn't enabled", func(t *testing.T) { + logger := flogging.MustGetLogger("foo") + logger2 := logger.Named("bar") + flogging.ActivateSpec("foo=fatal:foo.bar=error") + logger.Error("from foo") + logger2.Error("from bar") + require.NotContains(t, buf.String(), "from foo") + require.Contains(t, buf.String(), "from bar") + }) +} + +func TestInvalidLoggerName(t *testing.T) { + names := []string{"test*", ".test", "test.", ".", ""} + for _, name := range names { + t.Run(name, func(t *testing.T) { + msg := fmt.Sprintf("invalid logger name: %s", name) + require.PanicsWithValue(t, msg, func() { flogging.MustGetLogger(name) }) + }) + } +} + +func TestCheck(t *testing.T) { + l := &flogging.Logging{} + observer := &mock.Observer{} + e := zapcore.Entry{} + + // set observer + l.SetObserver(observer) + l.Check(e, nil) + require.Equal(t, 1, observer.CheckCallCount()) + e, ce := observer.CheckArgsForCall(0) + require.Equal(t, e, zapcore.Entry{}) + require.Nil(t, ce) + + l.WriteEntry(e, nil) + require.Equal(t, 1, observer.WriteEntryCallCount()) + e, f := observer.WriteEntryArgsForCall(0) + require.Equal(t, e, zapcore.Entry{}) + require.Nil(t, f) + + // remove observer + l.SetObserver(nil) + l.Check(zapcore.Entry{}, nil) + require.Equal(t, 1, observer.CheckCallCount()) +} + +func TestLoggerCoreCheck(t *testing.T) { + logging, err := flogging.New(flogging.Config{}) + require.NoError(t, err) + + logger := logging.ZapLogger("foo") + + err = logging.ActivateSpec("info") + require.NoError(t, err) + require.False(t, logger.Core().Enabled(zapcore.DebugLevel), "debug should not be enabled at info level") + + err = logging.ActivateSpec("debug") + require.NoError(t, err) + require.True(t, logger.Core().Enabled(zapcore.DebugLevel), "debug should now be enabled at debug level") +} diff --git a/v2/common/flogging/mock/observer.go b/v2/common/flogging/mock/observer.go new file mode 100644 index 0000000..0afe3b1 --- /dev/null +++ b/v2/common/flogging/mock/observer.go @@ -0,0 +1,123 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mock + +import ( + "sync" + + "github.com/IBM/idemix/v2/common/flogging" + "go.uber.org/zap/zapcore" +) + +type Observer struct { + CheckStub func(zapcore.Entry, *zapcore.CheckedEntry) + checkMutex sync.RWMutex + checkArgsForCall []struct { + arg1 zapcore.Entry + arg2 *zapcore.CheckedEntry + } + WriteEntryStub func(zapcore.Entry, []zapcore.Field) + writeEntryMutex sync.RWMutex + writeEntryArgsForCall []struct { + arg1 zapcore.Entry + arg2 []zapcore.Field + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *Observer) Check(arg1 zapcore.Entry, arg2 *zapcore.CheckedEntry) { + fake.checkMutex.Lock() + fake.checkArgsForCall = append(fake.checkArgsForCall, struct { + arg1 zapcore.Entry + arg2 *zapcore.CheckedEntry + }{arg1, arg2}) + fake.recordInvocation("Check", []interface{}{arg1, arg2}) + fake.checkMutex.Unlock() + if fake.CheckStub != nil { + fake.CheckStub(arg1, arg2) + } +} + +func (fake *Observer) CheckCallCount() int { + fake.checkMutex.RLock() + defer fake.checkMutex.RUnlock() + return len(fake.checkArgsForCall) +} + +func (fake *Observer) CheckCalls(stub func(zapcore.Entry, *zapcore.CheckedEntry)) { + fake.checkMutex.Lock() + defer fake.checkMutex.Unlock() + fake.CheckStub = stub +} + +func (fake *Observer) CheckArgsForCall(i int) (zapcore.Entry, *zapcore.CheckedEntry) { + fake.checkMutex.RLock() + defer fake.checkMutex.RUnlock() + argsForCall := fake.checkArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Observer) WriteEntry(arg1 zapcore.Entry, arg2 []zapcore.Field) { + var arg2Copy []zapcore.Field + if arg2 != nil { + arg2Copy = make([]zapcore.Field, len(arg2)) + copy(arg2Copy, arg2) + } + fake.writeEntryMutex.Lock() + fake.writeEntryArgsForCall = append(fake.writeEntryArgsForCall, struct { + arg1 zapcore.Entry + arg2 []zapcore.Field + }{arg1, arg2Copy}) + fake.recordInvocation("WriteEntry", []interface{}{arg1, arg2Copy}) + fake.writeEntryMutex.Unlock() + if fake.WriteEntryStub != nil { + fake.WriteEntryStub(arg1, arg2) + } +} + +func (fake *Observer) WriteEntryCallCount() int { + fake.writeEntryMutex.RLock() + defer fake.writeEntryMutex.RUnlock() + return len(fake.writeEntryArgsForCall) +} + +func (fake *Observer) WriteEntryCalls(stub func(zapcore.Entry, []zapcore.Field)) { + fake.writeEntryMutex.Lock() + defer fake.writeEntryMutex.Unlock() + fake.WriteEntryStub = stub +} + +func (fake *Observer) WriteEntryArgsForCall(i int) (zapcore.Entry, []zapcore.Field) { + fake.writeEntryMutex.RLock() + defer fake.writeEntryMutex.RUnlock() + argsForCall := fake.writeEntryArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Observer) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.checkMutex.RLock() + defer fake.checkMutex.RUnlock() + fake.writeEntryMutex.RLock() + defer fake.writeEntryMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *Observer) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ flogging.Observer = new(Observer) diff --git a/v2/common/flogging/mock/write_syncer.go b/v2/common/flogging/mock/write_syncer.go new file mode 100644 index 0000000..6c7990d --- /dev/null +++ b/v2/common/flogging/mock/write_syncer.go @@ -0,0 +1,180 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mock + +import ( + "sync" +) + +type WriteSyncer struct { + SyncStub func() error + syncMutex sync.RWMutex + syncArgsForCall []struct { + } + syncReturns struct { + result1 error + } + syncReturnsOnCall map[int]struct { + result1 error + } + WriteStub func([]byte) (int, error) + writeMutex sync.RWMutex + writeArgsForCall []struct { + arg1 []byte + } + writeReturns struct { + result1 int + result2 error + } + writeReturnsOnCall map[int]struct { + result1 int + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *WriteSyncer) Sync() error { + fake.syncMutex.Lock() + ret, specificReturn := fake.syncReturnsOnCall[len(fake.syncArgsForCall)] + fake.syncArgsForCall = append(fake.syncArgsForCall, struct { + }{}) + fake.recordInvocation("Sync", []interface{}{}) + fake.syncMutex.Unlock() + if fake.SyncStub != nil { + return fake.SyncStub() + } + if specificReturn { + return ret.result1 + } + fakeReturns := fake.syncReturns + return fakeReturns.result1 +} + +func (fake *WriteSyncer) SyncCallCount() int { + fake.syncMutex.RLock() + defer fake.syncMutex.RUnlock() + return len(fake.syncArgsForCall) +} + +func (fake *WriteSyncer) SyncCalls(stub func() error) { + fake.syncMutex.Lock() + defer fake.syncMutex.Unlock() + fake.SyncStub = stub +} + +func (fake *WriteSyncer) SyncReturns(result1 error) { + fake.syncMutex.Lock() + defer fake.syncMutex.Unlock() + fake.SyncStub = nil + fake.syncReturns = struct { + result1 error + }{result1} +} + +func (fake *WriteSyncer) SyncReturnsOnCall(i int, result1 error) { + fake.syncMutex.Lock() + defer fake.syncMutex.Unlock() + fake.SyncStub = nil + if fake.syncReturnsOnCall == nil { + fake.syncReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.syncReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *WriteSyncer) Write(arg1 []byte) (int, error) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.writeMutex.Lock() + ret, specificReturn := fake.writeReturnsOnCall[len(fake.writeArgsForCall)] + fake.writeArgsForCall = append(fake.writeArgsForCall, struct { + arg1 []byte + }{arg1Copy}) + fake.recordInvocation("Write", []interface{}{arg1Copy}) + fake.writeMutex.Unlock() + if fake.WriteStub != nil { + return fake.WriteStub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.writeReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *WriteSyncer) WriteCallCount() int { + fake.writeMutex.RLock() + defer fake.writeMutex.RUnlock() + return len(fake.writeArgsForCall) +} + +func (fake *WriteSyncer) WriteCalls(stub func([]byte) (int, error)) { + fake.writeMutex.Lock() + defer fake.writeMutex.Unlock() + fake.WriteStub = stub +} + +func (fake *WriteSyncer) WriteArgsForCall(i int) []byte { + fake.writeMutex.RLock() + defer fake.writeMutex.RUnlock() + argsForCall := fake.writeArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *WriteSyncer) WriteReturns(result1 int, result2 error) { + fake.writeMutex.Lock() + defer fake.writeMutex.Unlock() + fake.WriteStub = nil + fake.writeReturns = struct { + result1 int + result2 error + }{result1, result2} +} + +func (fake *WriteSyncer) WriteReturnsOnCall(i int, result1 int, result2 error) { + fake.writeMutex.Lock() + defer fake.writeMutex.Unlock() + fake.WriteStub = nil + if fake.writeReturnsOnCall == nil { + fake.writeReturnsOnCall = make(map[int]struct { + result1 int + result2 error + }) + } + fake.writeReturnsOnCall[i] = struct { + result1 int + result2 error + }{result1, result2} +} + +func (fake *WriteSyncer) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.syncMutex.RLock() + defer fake.syncMutex.RUnlock() + fake.writeMutex.RLock() + defer fake.writeMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *WriteSyncer) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} diff --git a/v2/common/flogging/zap.go b/v2/common/flogging/zap.go new file mode 100644 index 0000000..6319833 --- /dev/null +++ b/v2/common/flogging/zap.go @@ -0,0 +1,105 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging + +import ( + "fmt" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zapgrpc" +) + +// NewZapLogger creates a zap logger around a new zap.Core. The core will use +// the provided encoder and sinks and a level enabler that is associated with +// the provided logger name. The logger that is returned will be named the same +// as the logger. +func NewZapLogger(core zapcore.Core, options ...zap.Option) *zap.Logger { + return zap.New( + core, + append([]zap.Option{ + zap.AddCaller(), + zap.AddStacktrace(zapcore.ErrorLevel), + }, options...)..., + ) +} + +// NewGRPCLogger creates a grpc.Logger that delegates to a zap.Logger. +func NewGRPCLogger(l *zap.Logger) *zapgrpc.Logger { + l = l.WithOptions( + zap.AddCaller(), + zap.AddCallerSkip(3), + ) + return zapgrpc.NewLogger(l, zapgrpc.WithDebug()) +} + +// NewFabricLogger creates a logger that delegates to the zap.SugaredLogger. +func NewFabricLogger(l *zap.Logger, options ...zap.Option) *FabricLogger { + return &FabricLogger{ + s: l.WithOptions(append(options, zap.AddCallerSkip(1))...).Sugar(), + } +} + +// A FabricLogger is an adapter around a zap.SugaredLogger that provides +// structured logging capabilities while preserving much of the legacy logging +// behavior. +// +// The most significant difference between the FabricLogger and the +// zap.SugaredLogger is that methods without a formatting suffix (f or w) build +// the log entry message with fmt.Sprintln instead of fmt.Sprint. Without this +// change, arguments are not separated by spaces. +type FabricLogger struct{ s *zap.SugaredLogger } + +func (f *FabricLogger) DPanic(args ...interface{}) { f.s.DPanicf(formatArgs(args)) } +func (f *FabricLogger) DPanicf(template string, args ...interface{}) { f.s.DPanicf(template, args...) } +func (f *FabricLogger) DPanicw(msg string, kvPairs ...interface{}) { f.s.DPanicw(msg, kvPairs...) } +func (f *FabricLogger) Debug(args ...interface{}) { f.s.Debugf(formatArgs(args)) } +func (f *FabricLogger) Debugf(template string, args ...interface{}) { f.s.Debugf(template, args...) } +func (f *FabricLogger) Debugw(msg string, kvPairs ...interface{}) { f.s.Debugw(msg, kvPairs...) } +func (f *FabricLogger) Error(args ...interface{}) { f.s.Errorf(formatArgs(args)) } +func (f *FabricLogger) Errorf(template string, args ...interface{}) { f.s.Errorf(template, args...) } +func (f *FabricLogger) Errorw(msg string, kvPairs ...interface{}) { f.s.Errorw(msg, kvPairs...) } +func (f *FabricLogger) Fatal(args ...interface{}) { f.s.Fatalf(formatArgs(args)) } +func (f *FabricLogger) Fatalf(template string, args ...interface{}) { f.s.Fatalf(template, args...) } +func (f *FabricLogger) Fatalw(msg string, kvPairs ...interface{}) { f.s.Fatalw(msg, kvPairs...) } +func (f *FabricLogger) Info(args ...interface{}) { f.s.Infof(formatArgs(args)) } +func (f *FabricLogger) Infof(template string, args ...interface{}) { f.s.Infof(template, args...) } +func (f *FabricLogger) Infow(msg string, kvPairs ...interface{}) { f.s.Infow(msg, kvPairs...) } +func (f *FabricLogger) Panic(args ...interface{}) { f.s.Panicf(formatArgs(args)) } +func (f *FabricLogger) Panicf(template string, args ...interface{}) { f.s.Panicf(template, args...) } +func (f *FabricLogger) Panicw(msg string, kvPairs ...interface{}) { f.s.Panicw(msg, kvPairs...) } +func (f *FabricLogger) Warn(args ...interface{}) { f.s.Warnf(formatArgs(args)) } +func (f *FabricLogger) Warnf(template string, args ...interface{}) { f.s.Warnf(template, args...) } +func (f *FabricLogger) Warnw(msg string, kvPairs ...interface{}) { f.s.Warnw(msg, kvPairs...) } +func (f *FabricLogger) Warning(args ...interface{}) { f.s.Warnf(formatArgs(args)) } +func (f *FabricLogger) Warningf(template string, args ...interface{}) { f.s.Warnf(template, args...) } + +// for backwards compatibility +func (f *FabricLogger) Critical(args ...interface{}) { f.s.Errorf(formatArgs(args)) } +func (f *FabricLogger) Criticalf(template string, args ...interface{}) { f.s.Errorf(template, args...) } +func (f *FabricLogger) Notice(args ...interface{}) { f.s.Infof(formatArgs(args)) } +func (f *FabricLogger) Noticef(template string, args ...interface{}) { f.s.Infof(template, args...) } + +func (f *FabricLogger) Named(name string) *FabricLogger { return &FabricLogger{s: f.s.Named(name)} } +func (f *FabricLogger) Sync() error { return f.s.Sync() } +func (f *FabricLogger) Zap() *zap.Logger { return f.s.Desugar() } + +func (f *FabricLogger) IsEnabledFor(level zapcore.Level) bool { + return f.s.Desugar().Core().Enabled(level) +} + +func (f *FabricLogger) With(args ...interface{}) *FabricLogger { + return &FabricLogger{s: f.s.With(args...)} +} + +func (f *FabricLogger) WithOptions(opts ...zap.Option) *FabricLogger { + l := f.s.Desugar().WithOptions(opts...) + return &FabricLogger{s: l.Sugar()} +} + +func formatArgs(args []interface{}) string { return strings.TrimSuffix(fmt.Sprintln(args...), "\n") } diff --git a/v2/common/flogging/zap_test.go b/v2/common/flogging/zap_test.go new file mode 100644 index 0000000..e7805f4 --- /dev/null +++ b/v2/common/flogging/zap_test.go @@ -0,0 +1,338 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package flogging_test + +import ( + "bytes" + "errors" + "io/ioutil" + "testing" + + "github.com/IBM/idemix/v2/common/flogging" + "github.com/IBM/idemix/v2/common/flogging/fabenc" + "github.com/IBM/idemix/v2/common/flogging/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + "google.golang.org/grpc/grpclog" +) + +func TestFabricLoggerEncoding(t *testing.T) { + formatters, err := fabenc.ParseFormat("%{color}[%{module}] %{shortfunc} -> %{level:.4s}%{color:reset} %{message}") + require.NoError(t, err) + enc := fabenc.NewFormatEncoder(formatters...) + + buf := &bytes.Buffer{} + core := zapcore.NewCore(enc, zapcore.AddSync(buf), zap.NewAtomicLevel()) + zl := flogging.NewZapLogger(core).Named("test").With(zap.String("extra", "field")) + fl := flogging.NewFabricLogger(zl) + + buf.Reset() + fl.Info("string value", 0, 1.23, struct{}{}) + require.Equal(t, "\x1b[34m[test] TestFabricLoggerEncoding -> INFO\x1b[0m string value 0 1.23 {} extra=field\n", buf.String()) + + buf.Reset() + fl.Infof("string %s, %d, %.3f, %v", "strval", 0, 1.23, struct{}{}) + require.Equal(t, "\x1b[34m[test] TestFabricLoggerEncoding -> INFO\x1b[0m string strval, 0, 1.230, {} extra=field\n", buf.String()) + + buf.Reset() + fl.Infow("this is a message", "int", 0, "float", 1.23, "struct", struct{}{}) + require.Equal(t, "\x1b[34m[test] TestFabricLoggerEncoding -> INFO\x1b[0m this is a message extra=field int=0 float=1.23 struct={}\n", buf.String()) +} + +func TestFabricLogger(t *testing.T) { + var enabler zap.LevelEnablerFunc = func(l zapcore.Level) bool { return true } + + var tests = []struct { + desc string + f func(fl *flogging.FabricLogger) + level zapcore.Level + message string + fields []zapcore.Field + panics bool + }{ + { + desc: "DPanic", + f: func(fl *flogging.FabricLogger) { fl.DPanic("arg1", "arg2") }, + level: zapcore.DPanicLevel, + message: "arg1 arg2", + fields: []zapcore.Field{}, + }, + { + desc: "DPanicf", + f: func(fl *flogging.FabricLogger) { fl.DPanicf("panic: %s, %d", "reason", 99) }, + level: zapcore.DPanicLevel, + message: "panic: reason, 99", + fields: []zapcore.Field{}, + }, + { + desc: "DPanicw", + f: func(fl *flogging.FabricLogger) { fl.DPanicw("I'm in a panic", "reason", "something", "code", 99) }, + level: zapcore.DPanicLevel, + message: "I'm in a panic", + fields: []zapcore.Field{zap.String("reason", "something"), zap.Int("code", 99)}, + }, + { + desc: "Debug", + f: func(fl *flogging.FabricLogger) { fl.Debug("arg1", "arg2") }, + level: zapcore.DebugLevel, + message: "arg1 arg2", + fields: []zapcore.Field{}, + }, + { + desc: "Debugf", + f: func(fl *flogging.FabricLogger) { fl.Debugf("debug: %s, %d", "goo", 99) }, + level: zapcore.DebugLevel, + message: "debug: goo, 99", + fields: []zapcore.Field{}, + }, + { + desc: "Debugw", + f: func(fl *flogging.FabricLogger) { fl.Debugw("debug data", "key", "value") }, + level: zapcore.DebugLevel, + message: "debug data", + fields: []zapcore.Field{zap.String("key", "value")}, + }, + { + desc: "Error", + f: func(fl *flogging.FabricLogger) { fl.Error("oh noes", errors.New("bananas")) }, + level: zapcore.ErrorLevel, + message: "oh noes bananas", + fields: []zapcore.Field{}, + }, + { + desc: "Errorf", + f: func(fl *flogging.FabricLogger) { fl.Errorf("error: %s", errors.New("bananas")) }, + level: zapcore.ErrorLevel, + message: "error: bananas", + fields: []zapcore.Field{}, + }, + { + desc: "Errorw", + f: func(fl *flogging.FabricLogger) { fl.Errorw("something failed", "err", errors.New("bananas")) }, + level: zapcore.ErrorLevel, + message: "something failed", + fields: []zapcore.Field{zap.NamedError("err", errors.New("bananas"))}, + }, + { + desc: "Info", + f: func(fl *flogging.FabricLogger) { fl.Info("fyi", "things are great") }, + level: zapcore.InfoLevel, + message: "fyi things are great", + fields: []zapcore.Field{}, + }, + { + desc: "Infof", + f: func(fl *flogging.FabricLogger) { fl.Infof("fyi: %s", "things are great") }, + level: zapcore.InfoLevel, + message: "fyi: things are great", + fields: []zapcore.Field{}, + }, + { + desc: "Infow", + f: func(fl *flogging.FabricLogger) { fl.Infow("fyi", "fish", "are smelly", "fruit", "is sweet") }, + level: zapcore.InfoLevel, + message: "fyi", + fields: []zapcore.Field{zap.String("fish", "are smelly"), zap.String("fruit", "is sweet")}, + }, + { + desc: "Panic", + f: func(fl *flogging.FabricLogger) { fl.Panic("oh noes", errors.New("platypus")) }, + level: zapcore.PanicLevel, + message: "oh noes platypus", + fields: []zapcore.Field{}, + panics: true, + }, + { + desc: "Panicf", + f: func(fl *flogging.FabricLogger) { fl.Panicf("error: %s", errors.New("platypus")) }, + level: zapcore.PanicLevel, + message: "error: platypus", + fields: []zapcore.Field{}, + panics: true, + }, + { + desc: "Panicw", + f: func(fl *flogging.FabricLogger) { fl.Panicw("something failed", "err", errors.New("platypus")) }, + level: zapcore.PanicLevel, + message: "something failed", + fields: []zapcore.Field{zap.NamedError("err", errors.New("platypus"))}, + panics: true, + }, + { + desc: "Warn", + f: func(fl *flogging.FabricLogger) { fl.Warn("oh noes", errors.New("monkeys")) }, + level: zapcore.WarnLevel, + message: "oh noes monkeys", + fields: []zapcore.Field{}, + }, + { + desc: "Warnf", + f: func(fl *flogging.FabricLogger) { fl.Warnf("error: %s", errors.New("monkeys")) }, + level: zapcore.WarnLevel, + message: "error: monkeys", + fields: []zapcore.Field{}, + }, + { + desc: "Warnw", + f: func(fl *flogging.FabricLogger) { fl.Warnw("something is weird", "err", errors.New("monkeys")) }, + level: zapcore.WarnLevel, + message: "something is weird", + fields: []zapcore.Field{zap.NamedError("err", errors.New("monkeys"))}, + }, + { + desc: "Warning", + f: func(fl *flogging.FabricLogger) { fl.Warning("oh noes", errors.New("monkeys")) }, + level: zapcore.WarnLevel, + message: "oh noes monkeys", + fields: []zapcore.Field{}, + }, + { + desc: "Warningf", + f: func(fl *flogging.FabricLogger) { fl.Warningf("error: %s", errors.New("monkeys")) }, + level: zapcore.WarnLevel, + message: "error: monkeys", + fields: []zapcore.Field{}, + }, + { + desc: "With", + f: func(fl *flogging.FabricLogger) { fl.With("key", "value").Debug("cool messages", "and stuff") }, + level: zapcore.DebugLevel, + message: "cool messages and stuff", + fields: []zapcore.Field{zap.String("key", "value")}, + }, + { + desc: "WithOptions", + f: func(fl *flogging.FabricLogger) { + fl.WithOptions(zap.Fields(zap.String("optionkey", "optionvalue"))).Debug("cool messages", "and stuff") + }, + level: zapcore.DebugLevel, + message: "cool messages and stuff", + fields: []zapcore.Field{zap.String("optionkey", "optionvalue")}, + }, + { + desc: "Critical", + f: func(fl *flogging.FabricLogger) { fl.Critical("critical as error", errors.New("kiwi")) }, + level: zapcore.ErrorLevel, + message: "critical as error kiwi", + fields: []zapcore.Field{}, + }, + { + desc: "Criticalf", + f: func(fl *flogging.FabricLogger) { fl.Criticalf("critical: %s", errors.New("kiwi")) }, + level: zapcore.ErrorLevel, + message: "critical: kiwi", + fields: []zapcore.Field{}, + }, + { + desc: "Notice", + f: func(fl *flogging.FabricLogger) { fl.Notice("notice", "as info") }, + level: zapcore.InfoLevel, + message: "notice as info", + fields: []zapcore.Field{}, + }, + { + desc: "Noticef", + f: func(fl *flogging.FabricLogger) { fl.Noticef("notice: %s", "this is info") }, + level: zapcore.InfoLevel, + message: "notice: this is info", + fields: []zapcore.Field{}, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + core, logs := observer.New(enabler) + fl := flogging.NewFabricLogger(zap.New(core)).Named("lname") + + if tc.panics { + require.Panics(t, func() { tc.f(fl) }) + } else { + tc.f(fl) + } + + err := fl.Sync() + require.NoError(t, err) + + entries := logs.All() + require.Len(t, entries, 1) + entry := entries[0] + + require.Equal(t, tc.level, entry.Level) + require.Equal(t, tc.message, entry.Message) + require.Equal(t, tc.fields, entry.Context) + require.Equal(t, "lname", entry.LoggerName) + }) + } +} + +func TestIsEnabledFor(t *testing.T) { + formatters, err := fabenc.ParseFormat("%{color}[%{module}] %{shortfunc} -> %{level:.4s}%{color:reset} %{message}") + require.NoError(t, err) + enc := fabenc.NewFormatEncoder(formatters...) + + enablerCallCount := 0 + enabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { + enablerCallCount++ + return l == zapcore.ErrorLevel + }) + + core := zapcore.NewCore(enc, zapcore.AddSync(ioutil.Discard), enabler) + zl := zap.New(core).Named("test") + fl := flogging.NewFabricLogger(zl) + + require.True(t, fl.IsEnabledFor(zapcore.ErrorLevel)) + require.False(t, fl.IsEnabledFor(zapcore.PanicLevel)) + require.Equal(t, 2, enablerCallCount) +} + +func logCaller(l grpclog.Logger, msg string) { l.Println(msg) } +func callWrapper(l grpclog.Logger, msg string) { logCaller(l, msg) } + +func TestGRPCLogger(t *testing.T) { + // ensure it includes the name as module, logs at debug level, and the caller with appropriate skip level + formatters, err := fabenc.ParseFormat("%{module} %{level} %{shortfunc} %{message}") + require.NoError(t, err) + enc := fabenc.NewFormatEncoder(formatters...) + + buf := &bytes.Buffer{} + enabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { return true }) + core := zapcore.NewCore(enc, zapcore.AddSync(buf), enabler) + zl := zap.New(core).Named("grpc") + gl := flogging.NewGRPCLogger(zl) + + callWrapper(gl, "message") + require.Equal(t, "grpc DEBUG callWrapper message\n", buf.String()) +} + +// FAB-15432 +// +// When the FabricLogger is used, the zap Core check function should not be +// driven if the minimum log level is above the level we are logging at. +// In other words, with a log spec of "info", logging at Debug should prevent +// a call to Check while logging at Info will not. +func TestEnabledLevelCheck(t *testing.T) { + buf := &bytes.Buffer{} + logging, err := flogging.New(flogging.Config{ + LogSpec: "info", + Writer: buf, + }) + require.NoError(t, err) + + fakeObserver := &mock.Observer{} + logging.SetObserver(fakeObserver) + + logger := logging.ZapLogger("foo") + fabricLogger := flogging.NewFabricLogger(logger) + + fabricLogger.Debug("debug message") + require.Equal(t, 0, fakeObserver.CheckCallCount(), "Check should not have been called") + + fabricLogger.Info("info message") + require.Equal(t, 1, fakeObserver.CheckCallCount(), "Check should have been called") +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..a7f63d0 --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,49 @@ +module github.com/IBM/idemix/v2 + +go 1.22.6 + +require ( + github.com/IBM/idemix/bccsp/schemes/aries v0.0.0-20240612072411-114d281b442d + github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20240612072411-114d281b442d + github.com/IBM/idemix/bccsp/types v0.0.0-20240612072411-114d281b442d + github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da + github.com/golang/protobuf v1.5.4 + github.com/hyperledger/aries-bbs-go v0.0.0-20240528084656-761671ea73bc + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 + github.com/onsi/ginkgo/v2 v2.13.2 + github.com/onsi/gomega v1.31.0 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.9.0 + github.com/sykesm/zap-logfmt v0.0.4 + go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.65.0 +) + +require ( + github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.13.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect + github.com/kilic/bls12-381 v0.1.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + +replace ( + github.com/IBM/idemix/bccsp/schemes/aries => ../bccsp/schemes/aries/ + github.com/IBM/idemix/bccsp/types => ../bccsp/types/ +) diff --git a/v2/go.sum b/v2/go.sum new file mode 100644 index 0000000..19685d2 --- /dev/null +++ b/v2/go.sum @@ -0,0 +1,129 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20240612072411-114d281b442d h1:jIDz71083inpGWt9IPVGb0DoqyF7tQW/YH0gBLb6sKo= +github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20240612072411-114d281b442d/go.mod h1:FC0vVgNI6bv8GH0VTwjup+arwJ8Tau1iEhroWZ1oPwU= +github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da h1:qqGozq4tF6EOVnWoTgBoJGudRKKZXSAYnEtDggzTnsw= +github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da/go.mod h1:Tco9QzE3fQzjMS7nPbHDeFfydAzctStf1Pa8hsh6Hjs= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.13.0 h1:VPULb/v6bbYELAPTDFINEVaMTTybV5GLxDdcjnS+4oc= +github.com/consensys/gnark-crypto v0.13.0/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/hyperledger/aries-bbs-go v0.0.0-20240528084656-761671ea73bc h1:3Ykk6MtyfnlzMOQry9zkxsoLWpCWZwDPqehO/BJwArM= +github.com/hyperledger/aries-bbs-go v0.0.0-20240528084656-761671ea73bc/go.mod h1:Kofn6A6WWea1ZM8Rys5aBW9dszwJ7Ywa0kyyYL0TPYw= +github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= +github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/sykesm/zap-logfmt v0.0.4 h1:U2WzRvmIWG1wDLCFY3sz8UeEmsdHQjHFNlIdmroVFaI= +github.com/sykesm/zap-logfmt v0.0.4/go.mod h1:AuBd9xQjAe3URrWT1BBDk2v2onAZHkZkWRMiYZXiZWA= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/v2/idemix_roles.go b/v2/idemix_roles.go new file mode 100644 index 0000000..aa3a7a0 --- /dev/null +++ b/v2/idemix_roles.go @@ -0,0 +1,71 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package idemix + +import ( + m "github.com/hyperledger/fabric-protos-go-apiv2/msp" +) + +// Role : Represents a IdemixRole +type Role int32 + +// The expected roles are 4; We can combine them using a bitmask +const ( + MEMBER Role = 1 + ADMIN Role = 2 + CLIENT Role = 4 + PEER Role = 8 + // Next role values: 16, 32, 64 ... +) + +func (role Role) getValue() int { + return int(role) +} + +// checkRole Prove that the desired role is contained or not in the bitmask +func checkRole(bitmask int, role Role) bool { + return (bitmask & role.getValue()) == role.getValue() +} + +// getRoleMaskFromIdemixRoles Receive a list of roles to combine in a single bitmask +func getRoleMaskFromIdemixRoles(roles []Role) int { + mask := 0 + for _, role := range roles { + mask = mask | role.getValue() + } + return mask +} + +// GetRoleMaskFromIdemixRole return a bitmask for one role +func GetRoleMaskFromIdemixRole(role Role) int { + return getRoleMaskFromIdemixRoles([]Role{role}) +} + +// getIdemixRoleFromMSPRole gets a MSP Role type and returns the integer value +func getIdemixRoleFromMSPRole(role *m.MSPRole) int { + return getIdemixRoleFromMSPRoleType(role.GetRole()) +} + +// GetIdemixRoleFromMSPRoleType gets a MSP role type and returns the integer value +func getIdemixRoleFromMSPRoleType(rtype m.MSPRole_MSPRoleType) int { + return getIdemixRoleFromMSPRoleValue(int(rtype)) +} + +// getIdemixRoleFromMSPRoleValue Receives a MSP role value and returns the idemix equivalent +func getIdemixRoleFromMSPRoleValue(role int) int { + switch role { + case int(m.MSPRole_ADMIN): + return ADMIN.getValue() + case int(m.MSPRole_CLIENT): + return CLIENT.getValue() + case int(m.MSPRole_MEMBER): + return MEMBER.getValue() + case int(m.MSPRole_PEER): + return PEER.getValue() + default: + return MEMBER.getValue() + } +} diff --git a/v2/idemixmsp.go b/v2/idemixmsp.go new file mode 100644 index 0000000..2e40471 --- /dev/null +++ b/v2/idemixmsp.go @@ -0,0 +1,765 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "bytes" + "encoding/hex" + "fmt" + "io/ioutil" + "path/filepath" + "time" + + bccsp "github.com/IBM/idemix/bccsp/types" + idemix "github.com/IBM/idemix/v2/bccsp" + "github.com/IBM/idemix/v2/bccsp/keystore" + "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + "github.com/IBM/idemix/v2/common/flogging" + im "github.com/IBM/idemix/v2/idemixmsp" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + m "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/pkg/errors" + "go.uber.org/zap/zapcore" +) + +const ( + // AttributeIndexOU contains the index of the OU attribute in the idemix credential attributes + AttributeIndexOU = iota + + // AttributeIndexRole contains the index of the Role attribute in the idemix credential attributes + AttributeIndexRole + + // AttributeIndexEnrollmentId contains the index of the Enrollment ID attribute in the idemix credential attributes + AttributeIndexEnrollmentId + + // AttributeIndexRevocationHandle contains the index of the Revocation Handle attribute in the idemix credential attributes + AttributeIndexRevocationHandle +) + +const ( + // AttributeNameOU is the attribute name of the Organization Unit attribute + AttributeNameOU = "OU" + + // AttributeNameRole is the attribute name of the Role attribute + AttributeNameRole = "Role" + + // AttributeNameEnrollmentId is the attribute name of the Enrollment ID attribute + AttributeNameEnrollmentId = "EnrollmentID" + + // AttributeNameRevocationHandle is the attribute name of the revocation handle attribute + AttributeNameRevocationHandle = "RevocationHandle" +) + +type MSPVersion int + +const ( + MSPv1_0 = iota + MSPv1_1 + MSPv1_3 + MSPv1_4_3 +) + +// index of the revocation handle attribute in the credential +const rhIndex = 3 +const eidIndex = 2 + +type Idemixmsp struct { + csp bccsp.BCCSP + version MSPVersion + ipk bccsp.Key + signer *IdemixSigningIdentity + name string + revocationPK bccsp.Key + epoch int +} + +var mspLogger = flogging.MustGetLogger("idemix") +var mspIdentityLogger = flogging.MustGetLogger("idemix.identity") + +// NewIdemixMsp creates a new instance of idemixmsp +func NewIdemixMsp(version MSPVersion) (MSP, error) { + mspLogger.Debugf("Creating Idemix-based MSP instance") + + curve := math.Curves[math.FP256BN_AMCL] + csp, err := idemix.New(&keystore.Dummy{}, curve, &amcl.Fp256bn{C: curve}, true) + if err != nil { + panic(fmt.Sprintf("unexpected condition, error received [%s]", err)) + } + + msp := Idemixmsp{csp: csp} + msp.version = version + return &msp, nil +} + +// NewIdemixMspAries creates a new instance of idemixmsp +func NewIdemixMspAries(version MSPVersion) (MSP, error) { + mspLogger.Debugf("Creating Idemix-based MSP instance") + + curve := math.Curves[math.BLS12_381_BBS] + csp, err := idemix.NewAries(&keystore.Dummy{}, curve, &amcl.Gurvy{C: curve}, true) + if err != nil { + panic(fmt.Sprintf("unexpected condition, error received [%s]", err)) + } + + msp := Idemixmsp{csp: csp} + msp.version = version + return &msp, nil +} + +func (msp *Idemixmsp) Setup(conf1 *m.MSPConfig) error { + mspLogger.Debugf("Setting up Idemix-based MSP instance") + + if conf1 == nil { + return errors.Errorf("setup error: nil conf reference") + } + + var conf im.IdemixMSPConfig + err := proto.Unmarshal(conf1.Config, &conf) + if err != nil { + return errors.Wrap(err, "failed unmarshalling idemix msp config") + } + + msp.name = conf.Name + mspLogger.Debugf("Setting up Idemix MSP instance %s", msp.name) + + switch conf1.Type { + case int32(IDEMIX): + case int32(IDEMIX_ARIES): + default: + return errors.Errorf("setup error: config is not of type IDEMIX") + } + + // Import Issuer Public Key + IssuerPublicKey, err := msp.csp.KeyImport( + conf.Ipk, + &bccsp.IdemixIssuerPublicKeyImportOpts{ + Temporary: true, + AttributeNames: []string{ + AttributeNameOU, + AttributeNameRole, + AttributeNameEnrollmentId, + AttributeNameRevocationHandle, + }, + }) + if err != nil { + importErr, ok := errors.Cause(err).(*bccsp.IdemixIssuerPublicKeyImporterError) + if !ok { + panic("unexpected condition, BCCSP did not return the expected *bccsp.IdemixIssuerPublicKeyImporterError") + } + switch importErr.Type { + case bccsp.IdemixIssuerPublicKeyImporterUnmarshallingError: + return errors.WithMessage(err, "failed to unmarshal ipk from idemix msp config") + case bccsp.IdemixIssuerPublicKeyImporterHashError: + return errors.WithMessage(err, "setting the hash of the issuer public key failed") + case bccsp.IdemixIssuerPublicKeyImporterValidationError: + return errors.WithMessage(err, "cannot setup idemix msp with invalid public key") + case bccsp.IdemixIssuerPublicKeyImporterNumAttributesError: + fallthrough + case bccsp.IdemixIssuerPublicKeyImporterAttributeNameError: + return errors.Errorf("issuer public key must have have attributes OU, Role, EnrollmentId, and RevocationHandle") + default: + panic(fmt.Sprintf("unexpected condtion, issuer public key import error not valid, got [%d]", importErr.Type)) + } + } + msp.ipk = IssuerPublicKey + + // Import revocation public key + RevocationPublicKey, err := msp.csp.KeyImport( + conf.RevocationPk, + &bccsp.IdemixRevocationPublicKeyImportOpts{Temporary: true}, + ) + if err != nil { + return errors.WithMessage(err, "failed to import revocation public key") + } + msp.revocationPK = RevocationPublicKey + + if conf.Signer == nil { + // No credential in config, so we don't setup a default signer + mspLogger.Debug("idemix msp setup as verification only msp (no key material found)") + return nil + } + + // A credential is present in the config, so we setup a default signer + + // Import User secret key + UserKey, err := msp.csp.KeyImport(conf.Signer.Sk, &bccsp.IdemixUserSecretKeyImportOpts{Temporary: true}) + if err != nil { + return errors.WithMessage(err, "failed importing signer secret key") + } + + // Derive NymPublicKey + NymKey, err := msp.csp.KeyDeriv(UserKey, &bccsp.IdemixNymKeyDerivationOpts{Temporary: true, IssuerPK: IssuerPublicKey}) + if err != nil { + return errors.WithMessage(err, "failed deriving nym") + } + NymPublicKey, err := NymKey.PublicKey() + if err != nil { + return errors.Wrapf(err, "failed getting public nym key") + } + + role := &m.MSPRole{ + MspIdentifier: msp.name, + Role: m.MSPRole_MEMBER, + } + if checkRole(int(conf.Signer.Role), ADMIN) { + role.Role = m.MSPRole_ADMIN + } + + ou := &m.OrganizationUnit{ + MspIdentifier: msp.name, + OrganizationalUnitIdentifier: conf.Signer.OrganizationalUnitIdentifier, + CertifiersIdentifier: IssuerPublicKey.SKI(), + } + + enrollmentId := conf.Signer.EnrollmentId + + // Verify credential + valid, err := msp.csp.Verify( + UserKey, + conf.Signer.Cred, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte(conf.Signer.OrganizationalUnitIdentifier)}, + {Type: bccsp.IdemixIntAttribute, Value: getIdemixRoleFromMSPRole(role)}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte(enrollmentId)}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + }, + ) + if err != nil || !valid { + return errors.WithMessage(err, "Credential is not cryptographically valid") + } + + // Create the cryptographic evidence that this identity is valid + proof, err := msp.csp.Sign( + UserKey, + nil, + &bccsp.IdemixSignerOpts{ + Credential: conf.Signer.Cred, + Nym: NymKey, + IssuerPK: IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: rhIndex, + EidIndex: eidIndex, + CRI: conf.Signer.CredentialRevocationInformation, + }, + ) + if err != nil { + return errors.WithMessage(err, "Failed to setup cryptographic proof of identity") + } + + // Set up default signer + msp.signer = &IdemixSigningIdentity{ + Idemixidentity: newIdemixIdentity(msp, NymPublicKey, role, ou, proof), + Cred: conf.Signer.Cred, + UserKey: UserKey, + NymKey: NymKey, + enrollmentId: enrollmentId} + + return nil +} + +// GetVersion returns the version of this MSP +func (msp *Idemixmsp) GetVersion() MSPVersion { + return msp.version +} + +func (msp *Idemixmsp) GetType() ProviderType { + return IDEMIX +} + +func (msp *Idemixmsp) GetIdentifier() (string, error) { + return msp.name, nil +} + +func (msp *Idemixmsp) GetDefaultSigningIdentity() (SigningIdentity, error) { + mspLogger.Debugf("Obtaining default idemix signing identity") + + if msp.signer == nil { + return nil, errors.Errorf("no default signer setup") + } + return msp.signer, nil +} + +func (msp *Idemixmsp) DeserializeIdentity(serializedID []byte) (Identity, error) { + sID := &m.SerializedIdentity{} + err := proto.Unmarshal(serializedID, sID) + if err != nil { + return nil, errors.Wrap(err, "could not deserialize a SerializedIdentity") + } + + if sID.Mspid != msp.name { + return nil, errors.Errorf("expected MSP ID %s, received %s", msp.name, sID.Mspid) + } + + return msp.DeserializeIdentityInternal(sID.GetIdBytes()) +} + +func (msp *Idemixmsp) DeserializeIdentityInternal(serializedID []byte) (Identity, error) { + mspLogger.Debug("idemixmsp: deserializing identity") + serialized := new(im.SerializedIdemixIdentity) + err := proto.Unmarshal(serializedID, serialized) + if err != nil { + return nil, errors.Wrap(err, "could not deserialize a SerializedIdemixIdentity") + } + if serialized.NymX == nil || serialized.NymY == nil { + return nil, errors.Errorf("unable to deserialize idemix identity: pseudonym is invalid") + } + + // Import NymPublicKey + var rawNymPublicKey []byte + rawNymPublicKey = append(rawNymPublicKey, serialized.NymX...) + rawNymPublicKey = append(rawNymPublicKey, serialized.NymY...) + NymPublicKey, err := msp.csp.KeyImport( + rawNymPublicKey, + &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}, + ) + if err != nil { + return nil, errors.WithMessage(err, "failed to import nym public key") + } + + // OU + ou := &m.OrganizationUnit{} + err = proto.Unmarshal(serialized.Ou, ou) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the OU of the identity") + } + + // Role + role := &m.MSPRole{} + err = proto.Unmarshal(serialized.Role, role) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the role of the identity") + } + + return newIdemixIdentity(msp, NymPublicKey, role, ou, serialized.Proof), nil +} + +func (msp *Idemixmsp) Validate(id Identity) error { + var identity *Idemixidentity + switch t := id.(type) { + case *Idemixidentity: + identity = id.(*Idemixidentity) + case *IdemixSigningIdentity: + identity = id.(*IdemixSigningIdentity).Idemixidentity + default: + return errors.Errorf("identity type %T is not recognized", t) + } + + mspLogger.Debugf("Validating identity %+v", identity) + if identity.GetMSPIdentifier() != msp.name { + return errors.Errorf("the supplied identity does not belong to this msp") + } + return identity.verifyProof() +} + +func (id *Idemixidentity) verifyProof() error { + // Verify signature + valid, err := id.msp.csp.Verify( + id.msp.ipk, + id.associationProof, + nil, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: id.msp.revocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte(id.OU.OrganizationalUnitIdentifier)}, + {Type: bccsp.IdemixIntAttribute, Value: getIdemixRoleFromMSPRole(id.Role)}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: rhIndex, + EidIndex: eidIndex, + Epoch: id.msp.epoch, + }, + ) + if err == nil && !valid { + panic("unexpected condition, an error should be returned for an invalid signature") + } + + return err +} + +func (msp *Idemixmsp) SatisfiesPrincipal(id Identity, principal *m.MSPPrincipal) error { + err := msp.Validate(id) + if err != nil { + return errors.Wrap(err, "identity is not valid with respect to this MSP") + } + + return msp.satisfiesPrincipalValidated(id, principal) +} + +// satisfiesPrincipalValidated performs all the tasks of satisfiesPrincipal except the identity validation, +// such that combined principals will not cause multiple expensive identity validations. +func (msp *Idemixmsp) satisfiesPrincipalValidated(id Identity, principal *m.MSPPrincipal) error { + switch principal.PrincipalClassification { + // in this case, we have to check whether the + // identity has a role in the msp - member or admin + case m.MSPPrincipal_ROLE: + // Principal contains the msp role + mspRole := &m.MSPRole{} + err := proto.Unmarshal(principal.Principal, mspRole) + if err != nil { + return errors.Wrap(err, "could not unmarshal MSPRole from principal") + } + + // at first, we check whether the MSP + // identifier is the same as that of the identity + if mspRole.MspIdentifier != msp.name { + return errors.Errorf("the identity is a member of a different MSP (expected %s, got %s)", mspRole.MspIdentifier, id.GetMSPIdentifier()) + } + + // now we validate the different msp roles + switch mspRole.Role { + case m.MSPRole_MEMBER: + // in the case of member, we simply check + // whether this identity is valid for the MSP + mspLogger.Debugf("Checking if identity satisfies MEMBER role for %s", msp.name) + return nil + case m.MSPRole_ADMIN: + mspLogger.Debugf("Checking if identity satisfies ADMIN role for %s", msp.name) + if id.(*Idemixidentity).Role.Role != m.MSPRole_ADMIN { + return errors.Errorf("user is not an admin") + } + return nil + case m.MSPRole_PEER: + if msp.version >= MSPv1_3 { + return errors.Errorf("idemixmsp only supports client use, so it cannot satisfy an MSPRole PEER principal") + } + fallthrough + case m.MSPRole_CLIENT: + if msp.version >= MSPv1_3 { + return nil // any valid idemixmsp member must be a client + } + fallthrough + default: + return errors.Errorf("invalid MSP role type %d", int32(mspRole.Role)) + } + // in this case we have to serialize this instance + // and compare it byte-by-byte with Principal + case m.MSPPrincipal_IDENTITY: + mspLogger.Debugf("Checking if identity satisfies IDENTITY principal") + idBytes, err := id.Serialize() + if err != nil { + return errors.Wrap(err, "could not serialize this identity instance") + } + + rv := bytes.Compare(idBytes, principal.Principal) + if rv == 0 { + return nil + } + return errors.Errorf("the identities do not match") + + case m.MSPPrincipal_ORGANIZATION_UNIT: + ou := &m.OrganizationUnit{} + err := proto.Unmarshal(principal.Principal, ou) + if err != nil { + return errors.Wrap(err, "could not unmarshal OU from principal") + } + + mspLogger.Debugf("Checking if identity is part of OU \"%s\" of mspid \"%s\"", ou.OrganizationalUnitIdentifier, ou.MspIdentifier) + + // at first, we check whether the MSP + // identifier is the same as that of the identity + if ou.MspIdentifier != msp.name { + return errors.Errorf("the identity is a member of a different MSP (expected %s, got %s)", ou.MspIdentifier, id.GetMSPIdentifier()) + } + + if ou.OrganizationalUnitIdentifier != id.(*Idemixidentity).OU.OrganizationalUnitIdentifier { + return errors.Errorf("user is not part of the desired organizational unit") + } + + return nil + case m.MSPPrincipal_COMBINED: + if msp.version <= MSPv1_1 { + return errors.Errorf("Combined MSP Principals are unsupported in MSPv1_1") + } + + // Principal is a combination of multiple principals. + principals := &m.CombinedPrincipal{} + err := proto.Unmarshal(principal.Principal, principals) + if err != nil { + return errors.Wrap(err, "could not unmarshal CombinedPrincipal from principal") + } + // Return an error if there are no principals in the combined principal. + if len(principals.Principals) == 0 { + return errors.New("no principals in CombinedPrincipal") + } + // Recursively call msp.SatisfiesPrincipal for all combined principals. + // There is no limit for the levels of nesting for the combined principals. + for _, cp := range principals.Principals { + err = msp.satisfiesPrincipalValidated(id, cp) + if err != nil { + return err + } + } + // The identity satisfies all the principals + return nil + case m.MSPPrincipal_ANONYMITY: + if msp.version <= MSPv1_1 { + return errors.Errorf("Anonymity MSP Principals are unsupported in MSPv1_1") + } + + anon := &m.MSPIdentityAnonymity{} + err := proto.Unmarshal(principal.Principal, anon) + if err != nil { + return errors.Wrap(err, "could not unmarshal MSPIdentityAnonymity from principal") + } + switch anon.AnonymityType { + case m.MSPIdentityAnonymity_ANONYMOUS: + return nil + case m.MSPIdentityAnonymity_NOMINAL: + return errors.New("principal is nominal, but idemix MSP is anonymous") + default: + return errors.Errorf("unknown principal anonymity type: %d", anon.AnonymityType) + } + default: + return errors.Errorf("invalid principal type %d", int32(principal.PrincipalClassification)) + } +} + +// IsWellFormed checks if the given identity can be deserialized into its provider-specific . +// In this MSP implementation, an identity is considered well formed if it contains a +// marshaled SerializedIdemixIdentity protobuf message. +func (id *Idemixmsp) IsWellFormed(identity *m.SerializedIdentity) error { + sId := new(im.SerializedIdemixIdentity) + err := proto.Unmarshal(identity.IdBytes, sId) + if err != nil { + return errors.Wrap(err, "not an idemix identity") + } + return nil +} + +func (msp *Idemixmsp) GetTLSRootCerts() [][]byte { + // TODO + return nil +} + +func (msp *Idemixmsp) GetTLSIntermediateCerts() [][]byte { + // TODO + return nil +} + +type Idemixidentity struct { + NymPublicKey bccsp.Key + msp *Idemixmsp + id *IdentityIdentifier + Role *m.MSPRole + OU *m.OrganizationUnit + // associationProof contains cryptographic proof that this identity + // belongs to the MSP id.msp, i.e., it proves that the pseudonym + // is constructed from a secret key on which the CA issued a credential. + associationProof []byte +} + +func (id *Idemixidentity) Anonymous() bool { + return true +} + +func newIdemixIdentity(msp *Idemixmsp, NymPublicKey bccsp.Key, role *m.MSPRole, ou *m.OrganizationUnit, proof []byte) *Idemixidentity { + id := &Idemixidentity{} + id.NymPublicKey = NymPublicKey + id.msp = msp + id.Role = role + id.OU = ou + id.associationProof = proof + + raw, err := NymPublicKey.Bytes() + if err != nil { + panic(fmt.Sprintf("unexpected condition, failed marshalling nym public key [%s]", err)) + } + id.id = &IdentityIdentifier{ + Mspid: msp.name, + Id: bytes.NewBuffer(raw).String(), + } + + return id +} + +func (id *Idemixidentity) ExpiresAt() time.Time { + // Idemix MSP currently does not use expiration dates or revocation, + // so we return the zero time to indicate this. + return time.Time{} +} + +func (id *Idemixidentity) GetIdentifier() *IdentityIdentifier { + return id.id +} + +func (id *Idemixidentity) GetMSPIdentifier() string { + mspid, _ := id.msp.GetIdentifier() + return mspid +} + +func (id *Idemixidentity) GetOrganizationalUnits() []*OUIdentifier { + // we use the (serialized) public key of this MSP as the CertifiersIdentifier + certifiersIdentifier, err := id.msp.ipk.Bytes() + if err != nil { + mspIdentityLogger.Errorf("Failed to marshal ipk in GetOrganizationalUnits: %s", err) + return nil + } + + return []*OUIdentifier{{certifiersIdentifier, id.OU.OrganizationalUnitIdentifier}} +} + +func (id *Idemixidentity) Validate() error { + return id.msp.Validate(id) +} + +func (id *Idemixidentity) Verify(msg []byte, sig []byte) error { + if mspIdentityLogger.IsEnabledFor(zapcore.DebugLevel) { + mspIdentityLogger.Debugf("Verify Idemix sig: msg = %s", hex.Dump(msg)) + mspIdentityLogger.Debugf("Verify Idemix sig: sig = %s", hex.Dump(sig)) + } + + _, err := id.msp.csp.Verify( + id.NymPublicKey, + sig, + msg, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: id.msp.ipk, + }, + ) + return err +} + +func (id *Idemixidentity) SatisfiesPrincipal(principal *m.MSPPrincipal) error { + return id.msp.SatisfiesPrincipal(id, principal) +} + +func (id *Idemixidentity) Serialize() ([]byte, error) { + serialized := &im.SerializedIdemixIdentity{} + + raw, err := id.NymPublicKey.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "could not serialize nym of identity %s", id.id) + } + // This is an assumption on how the underlying idemix implementation work. + // TODO: change this in future version + serialized.NymX = raw[:len(raw)/2] + serialized.NymY = raw[len(raw)/2:] + ouBytes, err := proto.Marshal(id.OU) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal OU of identity %s", id.id) + } + + roleBytes, err := proto.Marshal(id.Role) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal role of identity %s", id.id) + } + + serialized.Ou = ouBytes + serialized.Role = roleBytes + serialized.Proof = id.associationProof + + idemixIDBytes, err := proto.Marshal(serialized) + if err != nil { + return nil, err + } + + sID := &m.SerializedIdentity{Mspid: id.GetMSPIdentifier(), IdBytes: idemixIDBytes} + idBytes, err := proto.Marshal(sID) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal a SerializedIdentity structure for identity %s", id.id) + } + + return idBytes, nil +} + +type IdemixSigningIdentity struct { + *Idemixidentity + Cred []byte + UserKey bccsp.Key + NymKey bccsp.Key + enrollmentId string +} + +func (id *IdemixSigningIdentity) Sign(msg []byte) ([]byte, error) { + mspLogger.Debugf("Idemix identity %s is signing", id.GetIdentifier()) + + sig, err := id.msp.csp.Sign( + id.UserKey, + msg, + &bccsp.IdemixNymSignerOpts{ + Nym: id.NymKey, + IssuerPK: id.msp.ipk, + }, + ) + if err != nil { + return nil, err + } + return sig, nil +} + +func (id *IdemixSigningIdentity) GetPublicVersion() Identity { + return id.Idemixidentity +} + +func readFile(file string) ([]byte, error) { + fileCont, err := ioutil.ReadFile(file) + if err != nil { + return nil, errors.Wrapf(err, "could not read file %s", file) + } + + return fileCont, nil +} + +const ( + IdemixConfigDirMsp = "msp" + IdemixConfigDirUser = "user" + IdemixConfigFileIssuerPublicKey = "IssuerPublicKey" + IdemixConfigFileRevocationPublicKey = "RevocationPublicKey" + IdemixConfigFileSigner = "SignerConfig" +) + +// GetIdemixMspConfig returns the configuration for the Idemix MSP +func GetIdemixMspConfig(dir string, ID string) (*m.MSPConfig, error) { + return GetIdemixMspConfigWithType(dir, ID, IDEMIX) +} + +// GetIdemixMspConfigWithType returns the configuration for the Idemix MSP of the specified type +func GetIdemixMspConfigWithType(dir string, ID string, mspType ProviderType) (*m.MSPConfig, error) { + ipkBytes, err := readFile(filepath.Join(dir, IdemixConfigDirMsp, IdemixConfigFileIssuerPublicKey)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read issuer public key file") + } + + revocationPkBytes, err := readFile(filepath.Join(dir, IdemixConfigDirMsp, IdemixConfigFileRevocationPublicKey)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read revocation public key file") + } + + idemixConfig := &im.IdemixMSPConfig{ + Name: ID, + Ipk: ipkBytes, + RevocationPk: revocationPkBytes, + } + + signerBytes, err := readFile(filepath.Join(dir, IdemixConfigDirUser, IdemixConfigFileSigner)) + if err == nil { + signerConfig := &im.IdemixMSPSignerConfig{} + err = proto.Unmarshal(signerBytes, signerConfig) + if err != nil { + return nil, err + } + idemixConfig.Signer = signerConfig + } + + confBytes, err := proto.Marshal(idemixConfig) + if err != nil { + return nil, err + } + + return &m.MSPConfig{Config: confBytes, Type: int32(mspType)}, nil +} diff --git a/v2/idemixmsp/identities.pb.go b/v2/idemixmsp/identities.pb.go new file mode 100644 index 0000000..81aba11 --- /dev/null +++ b/v2/idemixmsp/identities.pb.go @@ -0,0 +1,131 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: idemixmsp/identities.proto + +package idemixmsp + +import ( + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// This struct represents an Idemix Identity +// to be used to serialize it and deserialize it. +// The IdemixMSP will first serialize an idemix identity to bytes using +// this proto, and then uses these bytes as id_bytes in SerializedIdentity +type SerializedIdemixIdentity struct { + // nym_x is the X-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + NymX []byte `protobuf:"bytes,1,opt,name=nym_x,json=nymX,proto3" json:"nym_x,omitempty"` + // nym_y is the Y-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + NymY []byte `protobuf:"bytes,2,opt,name=nym_y,json=nymY,proto3" json:"nym_y,omitempty"` + // ou contains the organizational unit of the idemix identity + Ou []byte `protobuf:"bytes,3,opt,name=ou,proto3" json:"ou,omitempty"` + // role contains the role of this identity (e.g., ADMIN or MEMBER) + Role []byte `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` + // proof contains the cryptographic evidence that this identity is valid + Proof []byte `protobuf:"bytes,5,opt,name=proof,proto3" json:"proof,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SerializedIdemixIdentity) Reset() { *m = SerializedIdemixIdentity{} } +func (m *SerializedIdemixIdentity) String() string { return proto.CompactTextString(m) } +func (*SerializedIdemixIdentity) ProtoMessage() {} +func (*SerializedIdemixIdentity) Descriptor() ([]byte, []int) { + return fileDescriptor_cb8a4544fc71d2d8, []int{0} +} + +func (m *SerializedIdemixIdentity) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SerializedIdemixIdentity.Unmarshal(m, b) +} +func (m *SerializedIdemixIdentity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SerializedIdemixIdentity.Marshal(b, m, deterministic) +} +func (m *SerializedIdemixIdentity) XXX_Merge(src proto.Message) { + xxx_messageInfo_SerializedIdemixIdentity.Merge(m, src) +} +func (m *SerializedIdemixIdentity) XXX_Size() int { + return xxx_messageInfo_SerializedIdemixIdentity.Size(m) +} +func (m *SerializedIdemixIdentity) XXX_DiscardUnknown() { + xxx_messageInfo_SerializedIdemixIdentity.DiscardUnknown(m) +} + +var xxx_messageInfo_SerializedIdemixIdentity proto.InternalMessageInfo + +func (m *SerializedIdemixIdentity) GetNymX() []byte { + if m != nil { + return m.NymX + } + return nil +} + +func (m *SerializedIdemixIdentity) GetNymY() []byte { + if m != nil { + return m.NymY + } + return nil +} + +func (m *SerializedIdemixIdentity) GetOu() []byte { + if m != nil { + return m.Ou + } + return nil +} + +func (m *SerializedIdemixIdentity) GetRole() []byte { + if m != nil { + return m.Role + } + return nil +} + +func (m *SerializedIdemixIdentity) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func init() { + proto.RegisterType((*SerializedIdemixIdentity)(nil), "idemixmsp.SerializedIdemixIdentity") +} + +func init() { proto.RegisterFile("idemixmsp/identities.proto", fileDescriptor_cb8a4544fc71d2d8) } + +var fileDescriptor_cb8a4544fc71d2d8 = []byte{ + // 231 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x4c, 0x49, 0xcd, + 0xcd, 0xac, 0xc8, 0x2d, 0x2e, 0xd0, 0xcf, 0x4c, 0x49, 0xcd, 0x2b, 0xc9, 0x2c, 0xc9, 0x4c, 0x2d, + 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0xcb, 0x29, 0xd5, 0x71, 0x49, 0x04, 0xa7, + 0x16, 0x65, 0x26, 0xe6, 0x64, 0x56, 0xa5, 0xa6, 0x78, 0x82, 0x85, 0x3d, 0x21, 0xca, 0x2b, 0x85, + 0x84, 0xb9, 0x58, 0xf3, 0x2a, 0x73, 0xe3, 0x2b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x58, + 0xf2, 0x2a, 0x73, 0x23, 0x60, 0x82, 0x95, 0x12, 0x4c, 0x70, 0xc1, 0x48, 0x21, 0x3e, 0x2e, 0xa6, + 0xfc, 0x52, 0x09, 0x66, 0xb0, 0x08, 0x53, 0x7e, 0xa9, 0x90, 0x10, 0x17, 0x4b, 0x51, 0x7e, 0x4e, + 0xaa, 0x04, 0x0b, 0x44, 0x0d, 0x88, 0x2d, 0x24, 0xc2, 0xc5, 0x5a, 0x50, 0x94, 0x9f, 0x9f, 0x26, + 0xc1, 0x0a, 0x16, 0x84, 0x70, 0x9c, 0x5a, 0x19, 0xb9, 0x78, 0x93, 0xf3, 0x73, 0xf5, 0xe0, 0x2e, + 0x72, 0xe2, 0xf7, 0x84, 0x3b, 0x37, 0x00, 0xe4, 0xda, 0x00, 0xc6, 0x28, 0xf9, 0xf4, 0xcc, 0x92, + 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x4f, 0x27, 0x5f, 0x7d, 0x88, 0x62, 0x7d, 0xb8, + 0x9e, 0x45, 0x4c, 0xcc, 0x9e, 0x11, 0x11, 0xab, 0x98, 0x38, 0x3d, 0x61, 0x22, 0xa7, 0x90, 0xd8, + 0x8f, 0x98, 0x44, 0xe1, 0xec, 0x18, 0xf7, 0x00, 0x27, 0xdf, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, + 0xc4, 0x57, 0x48, 0x6a, 0x92, 0xd8, 0xc0, 0x21, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x6a, + 0xc5, 0x9b, 0x6e, 0x37, 0x01, 0x00, 0x00, +} diff --git a/v2/idemixmsp/identities.proto b/v2/idemixmsp/identities.proto new file mode 100644 index 0000000..48e64fa --- /dev/null +++ b/v2/idemixmsp/identities.proto @@ -0,0 +1,35 @@ + +// Copyright the Hyperledger Fabric contributors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +option go_package = "github.com/IBM/idemix/idemixmsp"; + +package idemixmsp; + +// This struct represents an Idemix Identity +// to be used to serialize it and deserialize it. +// The IdemixMSP will first serialize an idemix identity to bytes using +// this proto, and then uses these bytes as id_bytes in SerializedIdentity +message SerializedIdemixIdentity { + // nym_x is the X-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + bytes nym_x = 1; + + // nym_y is the Y-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + bytes nym_y = 2; + + // ou contains the organizational unit of the idemix identity + bytes ou = 3; + + // role contains the role of this identity (e.g., ADMIN or MEMBER) + bytes role = 4; + + // proof contains the cryptographic evidence that this identity is valid + bytes proof = 5; +} \ No newline at end of file diff --git a/v2/idemixmsp/msp_config.pb.go b/v2/idemixmsp/msp_config.pb.go new file mode 100644 index 0000000..7d22712 --- /dev/null +++ b/v2/idemixmsp/msp_config.pb.go @@ -0,0 +1,240 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: idemixmsp/msp_config.proto + +package idemixmsp + +import ( + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type IdemixMSPConfig struct { + // Name holds the identifier of the MSP + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // ipk represents the (serialized) issuer public key + Ipk []byte `protobuf:"bytes,2,opt,name=ipk,proto3" json:"ipk,omitempty"` + // signer may contain crypto material to configure a default signer + Signer *IdemixMSPSignerConfig `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` + // revocation_pk is the public key used for revocation of credentials + RevocationPk []byte `protobuf:"bytes,4,opt,name=revocation_pk,json=revocationPk,proto3" json:"revocation_pk,omitempty"` + // epoch represents the current epoch (time interval) used for revocation + Epoch int64 `protobuf:"varint,5,opt,name=epoch,proto3" json:"epoch,omitempty"` + // curve_id indicates which Elliptic Curve should be used + CurveId string `protobuf:"bytes,6,opt,name=curve_id,json=curveId,proto3" json:"curve_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IdemixMSPConfig) Reset() { *m = IdemixMSPConfig{} } +func (m *IdemixMSPConfig) String() string { return proto.CompactTextString(m) } +func (*IdemixMSPConfig) ProtoMessage() {} +func (*IdemixMSPConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_783ba658a7862c73, []int{0} +} + +func (m *IdemixMSPConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IdemixMSPConfig.Unmarshal(m, b) +} +func (m *IdemixMSPConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IdemixMSPConfig.Marshal(b, m, deterministic) +} +func (m *IdemixMSPConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdemixMSPConfig.Merge(m, src) +} +func (m *IdemixMSPConfig) XXX_Size() int { + return xxx_messageInfo_IdemixMSPConfig.Size(m) +} +func (m *IdemixMSPConfig) XXX_DiscardUnknown() { + xxx_messageInfo_IdemixMSPConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_IdemixMSPConfig proto.InternalMessageInfo + +func (m *IdemixMSPConfig) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *IdemixMSPConfig) GetIpk() []byte { + if m != nil { + return m.Ipk + } + return nil +} + +func (m *IdemixMSPConfig) GetSigner() *IdemixMSPSignerConfig { + if m != nil { + return m.Signer + } + return nil +} + +func (m *IdemixMSPConfig) GetRevocationPk() []byte { + if m != nil { + return m.RevocationPk + } + return nil +} + +func (m *IdemixMSPConfig) GetEpoch() int64 { + if m != nil { + return m.Epoch + } + return 0 +} + +func (m *IdemixMSPConfig) GetCurveId() string { + if m != nil { + return m.CurveId + } + return "" +} + +// IdemixMSPSIgnerConfig contains the crypto material to set up an idemix signing identity +type IdemixMSPSignerConfig struct { + // cred represents the serialized idemix credential of the default signer + Cred []byte `protobuf:"bytes,1,opt,name=cred,proto3" json:"cred,omitempty"` + // sk is the secret key of the default signer, corresponding to credential Cred + Sk []byte `protobuf:"bytes,2,opt,name=sk,proto3" json:"sk,omitempty"` + // organizational_unit_identifier defines the organizational unit the default signer is in + OrganizationalUnitIdentifier string `protobuf:"bytes,3,opt,name=organizational_unit_identifier,json=organizationalUnitIdentifier,proto3" json:"organizational_unit_identifier,omitempty"` + // role defines whether the default signer is admin, peer, member or client + Role int32 `protobuf:"varint,4,opt,name=role,proto3" json:"role,omitempty"` + // enrollment_id contains the enrollment id of this signer + EnrollmentId string `protobuf:"bytes,5,opt,name=enrollment_id,json=enrollmentId,proto3" json:"enrollment_id,omitempty"` + // credential_revocation_information contains a serialized CredentialRevocationInformation + CredentialRevocationInformation []byte `protobuf:"bytes,6,opt,name=credential_revocation_information,json=credentialRevocationInformation,proto3" json:"credential_revocation_information,omitempty"` + // RevocationHandle is the handle used to single out this credential and determine its revocation status + RevocationHandle string `protobuf:"bytes,7,opt,name=revocation_handle,json=revocationHandle,proto3" json:"revocation_handle,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IdemixMSPSignerConfig) Reset() { *m = IdemixMSPSignerConfig{} } +func (m *IdemixMSPSignerConfig) String() string { return proto.CompactTextString(m) } +func (*IdemixMSPSignerConfig) ProtoMessage() {} +func (*IdemixMSPSignerConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_783ba658a7862c73, []int{1} +} + +func (m *IdemixMSPSignerConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IdemixMSPSignerConfig.Unmarshal(m, b) +} +func (m *IdemixMSPSignerConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IdemixMSPSignerConfig.Marshal(b, m, deterministic) +} +func (m *IdemixMSPSignerConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdemixMSPSignerConfig.Merge(m, src) +} +func (m *IdemixMSPSignerConfig) XXX_Size() int { + return xxx_messageInfo_IdemixMSPSignerConfig.Size(m) +} +func (m *IdemixMSPSignerConfig) XXX_DiscardUnknown() { + xxx_messageInfo_IdemixMSPSignerConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_IdemixMSPSignerConfig proto.InternalMessageInfo + +func (m *IdemixMSPSignerConfig) GetCred() []byte { + if m != nil { + return m.Cred + } + return nil +} + +func (m *IdemixMSPSignerConfig) GetSk() []byte { + if m != nil { + return m.Sk + } + return nil +} + +func (m *IdemixMSPSignerConfig) GetOrganizationalUnitIdentifier() string { + if m != nil { + return m.OrganizationalUnitIdentifier + } + return "" +} + +func (m *IdemixMSPSignerConfig) GetRole() int32 { + if m != nil { + return m.Role + } + return 0 +} + +func (m *IdemixMSPSignerConfig) GetEnrollmentId() string { + if m != nil { + return m.EnrollmentId + } + return "" +} + +func (m *IdemixMSPSignerConfig) GetCredentialRevocationInformation() []byte { + if m != nil { + return m.CredentialRevocationInformation + } + return nil +} + +func (m *IdemixMSPSignerConfig) GetRevocationHandle() string { + if m != nil { + return m.RevocationHandle + } + return "" +} + +func init() { + proto.RegisterType((*IdemixMSPConfig)(nil), "idemixmsp.IdemixMSPConfig") + proto.RegisterType((*IdemixMSPSignerConfig)(nil), "idemixmsp.IdemixMSPSignerConfig") +} + +func init() { proto.RegisterFile("idemixmsp/msp_config.proto", fileDescriptor_783ba658a7862c73) } + +var fileDescriptor_783ba658a7862c73 = []byte{ + // 428 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0x41, 0x8b, 0xd4, 0x30, + 0x14, 0xc7, 0x49, 0x67, 0x67, 0xd6, 0xc6, 0xee, 0xba, 0x06, 0x17, 0xaa, 0x88, 0x5b, 0xd7, 0xcb, + 0x80, 0x30, 0x03, 0x7a, 0xf1, 0x3c, 0x0a, 0x1a, 0x61, 0xa0, 0x64, 0x11, 0x16, 0x11, 0x4a, 0xb6, + 0xc9, 0xcc, 0x84, 0x36, 0x49, 0x49, 0x33, 0x8b, 0x78, 0xf6, 0x93, 0x78, 0xf4, 0xea, 0x87, 0x10, + 0xfc, 0x18, 0x1e, 0xfd, 0x14, 0x92, 0xd7, 0xdd, 0xb6, 0x82, 0xb7, 0xff, 0x7b, 0xf9, 0xf5, 0xff, + 0xde, 0x3f, 0x0d, 0x7e, 0xa4, 0x84, 0xd4, 0xea, 0xb3, 0x6e, 0x9b, 0xa5, 0x6e, 0x9b, 0xa2, 0xb4, + 0x66, 0xa3, 0xb6, 0x8b, 0xc6, 0x59, 0x6f, 0x49, 0xdc, 0x9f, 0x9d, 0xff, 0x44, 0xf8, 0x1e, 0x85, + 0x6a, 0x7d, 0x91, 0xbf, 0x06, 0x88, 0x10, 0x7c, 0x60, 0xb8, 0x96, 0x29, 0xca, 0xd0, 0x3c, 0x66, + 0xa0, 0xc9, 0x09, 0x9e, 0xa8, 0xa6, 0x4a, 0xa3, 0x0c, 0xcd, 0x13, 0x16, 0x24, 0x79, 0x85, 0x67, + 0xad, 0xda, 0x1a, 0xe9, 0xd2, 0x49, 0x86, 0xe6, 0x77, 0x5f, 0x64, 0x8b, 0xde, 0x75, 0xd1, 0x3b, + 0x5e, 0x00, 0xd1, 0xf9, 0xb2, 0x1b, 0x9e, 0x3c, 0xc3, 0x47, 0x4e, 0x5e, 0xdb, 0x92, 0x7b, 0x65, + 0x4d, 0xd1, 0x54, 0xe9, 0x01, 0xb8, 0x26, 0x43, 0x33, 0xaf, 0xc8, 0x03, 0x3c, 0x95, 0x8d, 0x2d, + 0x77, 0xe9, 0x34, 0x43, 0xf3, 0x09, 0xeb, 0x0a, 0xf2, 0x10, 0xdf, 0x29, 0xf7, 0xee, 0x5a, 0x16, + 0x4a, 0xa4, 0x33, 0x58, 0xef, 0x10, 0x6a, 0x2a, 0xce, 0x7f, 0x44, 0xf8, 0xf4, 0xbf, 0x73, 0x43, + 0x9e, 0xd2, 0x49, 0x01, 0x79, 0x12, 0x06, 0x9a, 0x1c, 0xe3, 0xa8, 0xbd, 0x8d, 0x13, 0xb5, 0x15, + 0x79, 0x83, 0x9f, 0x58, 0xb7, 0xe5, 0x46, 0x7d, 0x81, 0x05, 0x78, 0x5d, 0xec, 0x8d, 0xf2, 0x85, + 0x12, 0xd2, 0x78, 0xb5, 0x51, 0x37, 0x29, 0x63, 0xf6, 0xf8, 0x5f, 0xea, 0x83, 0x51, 0x9e, 0xf6, + 0x4c, 0x98, 0xe4, 0x6c, 0x2d, 0x21, 0xd0, 0x94, 0x81, 0x0e, 0x69, 0xa5, 0x71, 0xb6, 0xae, 0xb5, + 0x34, 0xc1, 0x10, 0x02, 0xc5, 0x2c, 0x19, 0x9a, 0x54, 0x90, 0xf7, 0xf8, 0x69, 0x58, 0x2b, 0x18, + 0xf1, 0xba, 0x18, 0xdd, 0x8e, 0x32, 0x1b, 0xeb, 0x34, 0x68, 0x08, 0x9c, 0xb0, 0xb3, 0x01, 0x64, + 0x3d, 0x47, 0x07, 0x8c, 0x3c, 0xc7, 0xf7, 0x47, 0x06, 0x3b, 0x6e, 0x44, 0x2d, 0xd3, 0x43, 0x18, + 0x7a, 0x32, 0x1c, 0xbc, 0x83, 0xfe, 0xea, 0x2b, 0xc2, 0x47, 0xa5, 0xd5, 0xc3, 0xbf, 0x5b, 0x1d, + 0xaf, 0xdb, 0xa6, 0xbb, 0xb8, 0x3c, 0x3c, 0x96, 0x1c, 0x7d, 0x3c, 0xdb, 0x2a, 0xbf, 0xdb, 0x5f, + 0x2d, 0x4a, 0xab, 0x97, 0x74, 0xb5, 0x5e, 0x76, 0xec, 0xb2, 0xff, 0xe4, 0x5b, 0x34, 0xa1, 0x97, + 0x97, 0xdf, 0xa3, 0x98, 0xde, 0x76, 0x7e, 0x8d, 0xf4, 0xef, 0xe8, 0xb4, 0xd7, 0x9f, 0xde, 0xe6, + 0xab, 0xb5, 0xf4, 0x5c, 0x70, 0xcf, 0xff, 0x8c, 0x98, 0xab, 0x19, 0x3c, 0xcc, 0x97, 0x7f, 0x03, + 0x00, 0x00, 0xff, 0xff, 0xe8, 0x8d, 0x2a, 0xd8, 0xb6, 0x02, 0x00, 0x00, +} diff --git a/v2/idemixmsp/msp_config.proto b/v2/idemixmsp/msp_config.proto new file mode 100644 index 0000000..ea64dc2 --- /dev/null +++ b/v2/idemixmsp/msp_config.proto @@ -0,0 +1,56 @@ +// IdemixMSPConfig collects all the configuration information for +// an Idemix MSP. + + +// Copyright the Hyperledger Fabric contributors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; +option go_package = "github.com/IBM/idemix/idemixmsp"; + +package idemixmsp; + +message IdemixMSPConfig { + // Name holds the identifier of the MSP + string name = 1; + + // ipk represents the (serialized) issuer public key + bytes ipk = 2; + + // signer may contain crypto material to configure a default signer + IdemixMSPSignerConfig signer = 3; + + // revocation_pk is the public key used for revocation of credentials + bytes revocation_pk = 4; + + // epoch represents the current epoch (time interval) used for revocation + int64 epoch = 5; + + // curve_id indicates which Elliptic Curve should be used + string curve_id = 6; + } + + // IdemixMSPSIgnerConfig contains the crypto material to set up an idemix signing identity + message IdemixMSPSignerConfig { + // cred represents the serialized idemix credential of the default signer + bytes cred = 1; + + // sk is the secret key of the default signer, corresponding to credential Cred + bytes sk = 2; + + // organizational_unit_identifier defines the organizational unit the default signer is in + string organizational_unit_identifier = 3; + + // role defines whether the default signer is admin, peer, member or client + int32 role = 4; + + // enrollment_id contains the enrollment id of this signer + string enrollment_id = 5; + + // credential_revocation_information contains a serialized CredentialRevocationInformation + bytes credential_revocation_information = 6; + + // RevocationHandle is the handle used to single out this credential and determine its revocation status + string revocation_handle = 7; +} \ No newline at end of file diff --git a/v2/idemixmsp_aries_test.go b/v2/idemixmsp_aries_test.go new file mode 100644 index 0000000..4f1e148 --- /dev/null +++ b/v2/idemixmsp_aries_test.go @@ -0,0 +1,636 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/stretchr/testify/require" +) + +func TestSigningAries(t *testing.T) { + msp, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + msg := []byte("TestMessage") + sig, err := id.Sign(msg) + require.NoError(t, err) + + err = id.Verify(msg, sig) + require.NoError(t, err) + + err = id.Verify([]byte("OtherMessage"), sig) + require.Error(t, err) + require.Contains(t, err.Error(), "contribution is not zero") + + verMsp, err := setupWithTypeAndVersion("testdata/aries/MSP1Verifier", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + err = verMsp.Validate(id) + require.NoError(t, err) + _, err = verMsp.GetDefaultSigningIdentity() + require.Error(t, err) + require.Contains(t, err.Error(), "no default signer setup") +} + +func TestSigningBadAries(t *testing.T) { + msp, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + msg := []byte("TestMessage") + sig := []byte("barf") + + err = id.Verify(msg, sig) + require.Error(t, err) + require.Contains(t, err.Error(), "error unmarshalling signature") +} + +func TestIdentitySerializationAries(t *testing.T) { + msp, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + // Test serialization of identities + serializedID, err := id.Serialize() + require.NoError(t, err) + + verID, err := msp.DeserializeIdentity(serializedID) + require.NoError(t, err) + + err = verID.Validate() + require.NoError(t, err) + + err = msp.Validate(verID) + require.NoError(t, err) +} + +func TestIdentitySerializationBadAries(t *testing.T) { + msp, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + _, err = msp.DeserializeIdentity([]byte("barf")) + require.Error(t, err, "DeserializeIdentity should have failed for bad input") + require.Contains(t, err.Error(), "could not deserialize a SerializedIdentity") +} + +func TestIdentitySerializationWrongMSPAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1OU1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + msp2, err := setupWithTypeAndVersion("testdata/aries/MSP2OU1eid1/", "MSP2OU1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + id2, err := getDefaultSigner(msp2) + require.NoError(t, err) + + idBytes, err := id2.Serialize() + require.NoError(t, err) + + _, err = msp1.DeserializeIdentity(idBytes) + require.Error(t, err, "DeserializeIdentity should have failed for ID of other MSP") + require.Contains(t, err.Error(), "expected MSP ID MSP1OU1, received MSP2OU1") +} + +func TestPrincipalIdentityAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + idBytes, err := id1.Serialize() + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalIdentityWrongIdentityAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1OU1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + msp2, err := setupWithTypeAndVersion("testdata/aries/MSP2OU1eid1/", "MSP2OU1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id2, err := getDefaultSigner(msp2) + require.NoError(t, err) + + idBytes, err := id1.Serialize() + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id2.SatisfiesPrincipal(principal) + require.Error(t, err, "Identity MSP principal for different user should fail") + require.Contains(t, err.Error(), "the identities do not match") + +} + +func TestPrincipalIdentityBadIdentityAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1OU1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + idBytes := []byte("barf") + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Identity MSP principal for a bad principal should fail") + require.Contains(t, err.Error(), "the identities do not match") +} + +func TestAnonymityPrincipalAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_ANONYMOUS}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestAnonymityPrincipalBadAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_NOMINAL}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Idemix identity is anonymous and should not pass NOMINAL anonymity principal") + require.Contains(t, err.Error(), "principal is nominal, but idemix MSP is anonymous") +} + +func TestAnonymityPrincipalV11Aries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_1, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_NOMINAL}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err) + require.Contains(t, err.Error(), "Anonymity MSP Principals are unsupported in MSPv1_1") +} + +func TestIdemixIsWellFormedAries(t *testing.T) { + idemixMSP, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id, err := getDefaultSigner(idemixMSP) + require.NoError(t, err) + rawId, err := id.Serialize() + require.NoError(t, err) + sId := &msp.SerializedIdentity{} + err = proto.Unmarshal(rawId, sId) + require.NoError(t, err) + err = idemixMSP.IsWellFormed(sId) + require.NoError(t, err) + // Corrupt the identity bytes + sId.IdBytes = append(sId.IdBytes, 1) + err = idemixMSP.IsWellFormed(sId) + require.Error(t, err) + require.Contains(t, err.Error(), "not an idemix identity") +} + +func TestPrincipalOUAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalOUWrongOUAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: "DifferentOU", + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for user of different OU") + require.Contains(t, err.Error(), "user is not part of the desired organizational unit") + +} + +func TestPrincipalOUWrongMSPAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: "OU1", + MspIdentifier: "OtherMSP", + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for user of different MSP") + require.Contains(t, err.Error(), "the identity is a member of a different MSP") + +} + +func TestPrincipalOUBadAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + bytes := []byte("barf") + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for a bad OU principal") + require.Contains(t, err.Error(), "could not unmarshal OU from principal") +} + +func TestPrincipalRoleMemberAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) + + // Member should also satisfy client + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_CLIENT, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal = &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalRoleAdminAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1Admin/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + // Admin should also satisfy member + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal = &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalRoleNotPeerAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1Admin/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_PEER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Admin should not satisfy PEER principal") + require.Contains(t, err.Error(), "idemixmsp only supports client use, so it cannot satisfy an MSPRole PEER principal") +} + +func TestPrincipalRoleNotAdminAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Member should not satisfy Admin principal") + require.Contains(t, err.Error(), "user is not an admin") +} + +func TestPrincipalRoleWrongMSPAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: "OtherMSP"}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Role MSP principal should have failed for user of different MSP") + require.Contains(t, err.Error(), "the identity is a member of a different MSP") +} + +func TestPrincipalRoleBadRoleAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + // Make principal for nonexisting role 1234 + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: 1234, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Role MSP principal should have failed for a bad Role") + require.Contains(t, err.Error(), "invalid MSP role type") +} + +func TestPrincipalBadAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: 1234, + Principal: nil} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Principal with bad Classification should fail") + require.Contains(t, err.Error(), "invalid principal type") +} + +func TestPrincipalCombinedAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.NoError(t, err) +} + +func TestPrincipalCombinedBadAries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_3, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + // create combined principal requiring membership of OU1 in MSP1 and requiring admin role + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.Error(t, err, "non-admin member of OU1 in MSP1 should not satisfy principal admin and OU1 in MSP1") + require.Contains(t, err.Error(), "user is not an admin") +} + +func TestPrincipalCombinedV11Aries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_1, IDEMIX_ARIES) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.Error(t, err) + require.Contains(t, err.Error(), "Combined MSP Principals are unsupported in MSPv1_1") +} + +func TestRoleClientV11Aries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_1, IDEMIX_ARIES) + require.NoError(t, err) + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_CLIENT, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + err = id1.SatisfiesPrincipal(principalRole) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid MSP role type") +} + +func TestRolePeerV11Aries(t *testing.T) { + msp1, err := setupWithTypeAndVersion("testdata/aries/MSP1OU1eid1/", "MSP1", MSPv1_1, IDEMIX_ARIES) + require.NoError(t, err) + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_PEER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + err = id1.SatisfiesPrincipal(principalRole) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid MSP role type") +} diff --git a/v2/idemixmsp_test.go b/v2/idemixmsp_test.go new file mode 100644 index 0000000..2814bc2 --- /dev/null +++ b/v2/idemixmsp_test.go @@ -0,0 +1,771 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "testing" + + idemix "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto" + amclt "github.com/IBM/idemix/v2/bccsp/schemes/dlog/crypto/translator/amcl" + im "github.com/IBM/idemix/v2/idemixmsp" + math "github.com/IBM/mathlib" + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func setup(configPath string, ID string) (MSP, error) { + return setupWithTypeAndVersion(configPath, ID, MSPv1_3, IDEMIX) +} + +func setupWithVersion(configPath string, ID string, version MSPVersion) (MSP, error) { + return setupWithTypeAndVersion(configPath, ID, version, IDEMIX) +} + +func setupWithTypeAndVersion(configPath string, ID string, version MSPVersion, mspType ProviderType) (MSP, error) { + var msp MSP + var err error + switch mspType { + case IDEMIX: + msp, err = NewIdemixMsp(version) + case IDEMIX_ARIES: + msp, err = NewIdemixMspAries(version) + default: + panic("programming error") + } + if err != nil { + return nil, err + } + + conf, err := GetIdemixMspConfigWithType(configPath, ID, mspType) + if err != nil { + return nil, errors.Wrap(err, "Getting MSP config failed") + } + + err = msp.Setup(conf) + if err != nil { + return nil, errors.Wrap(err, "Setting up MSP failed") + } + return msp, nil +} + +func getDefaultSigner(msp MSP) (SigningIdentity, error) { + id, err := msp.GetDefaultSigningIdentity() + if err != nil { + return nil, errors.Wrap(err, "Getting default signing identity failed") + } + + err = id.Validate() + if err != nil { + return nil, errors.Wrap(err, "Default signing identity invalid") + } + + err = msp.Validate(id) + if err != nil { + return nil, errors.Wrap(err, "Default signing identity invalid") + } + + return id, nil +} + +func TestSetup(t *testing.T) { + msp, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + require.Equal(t, IDEMIX, msp.GetType()) +} + +func TestSetupBad(t *testing.T) { + curve := math.Curves[math.FP256BN_AMCL] + tr := &amclt.Fp256bn{ + C: curve, + } + + _, err := setup("testdata/idemix/badpath", "MSPID") + require.Error(t, err) + require.Contains(t, err.Error(), "Getting MSP config failed") + + msp1, err := NewIdemixMsp(MSPv1_3) + require.NoError(t, err) + + // Setup with nil config + err = msp1.Setup(nil) + require.Error(t, err) + require.Contains(t, err.Error(), "setup error: nil conf reference") + + // Setup with incorrect MSP type + conf := &msp.MSPConfig{Type: 1234, Config: nil} + err = msp1.Setup(conf) + require.Error(t, err) + require.Contains(t, err.Error(), "setup error: config is not of type IDEMIX") + + // Setup with bad idemix config bytes + conf = &msp.MSPConfig{Type: int32(IDEMIX), Config: []byte("barf")} + err = msp1.Setup(conf) + require.Error(t, err) + require.Contains(t, err.Error(), "failed unmarshalling idemix msp config") + + conf, err = GetIdemixMspConfigWithType("testdata/idemix/MSP1OU1", "IdemixMSP1", IDEMIX) + require.NoError(t, err) + idemixconfig := &im.IdemixMSPConfig{} + err = proto.Unmarshal(conf.Config, idemixconfig) + require.NoError(t, err) + + // Create MSP config with IPK with incorrect attribute names + idmx := &idemix.Idemix{ + Curve: curve, + } + rng, err := curve.Rand() + require.NoError(t, err) + key, err := idmx.NewIssuerKey([]string{}, rng, tr) + require.NoError(t, err) + ipkBytes, err := proto.Marshal(key.Ipk) + require.NoError(t, err) + idemixconfig.Ipk = ipkBytes + + idemixConfigBytes, err := proto.Marshal(idemixconfig) + require.NoError(t, err) + conf.Config = idemixConfigBytes + + err = msp1.Setup(conf) + require.Error(t, err) + require.Contains(t, err.Error(), "issuer public key must have have attributes OU, Role, EnrollmentId, and RevocationHandle") + + // Create MSP config with bad IPK bytes + ipkBytes = []byte("barf") + idemixconfig.Ipk = ipkBytes + + idemixConfigBytes, err = proto.Marshal(idemixconfig) + require.NoError(t, err) + conf.Config = idemixConfigBytes + + err = msp1.Setup(conf) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to unmarshal ipk from idemix msp config") +} + +func TestSigning(t *testing.T) { + msp, err := setup("testdata/idemix/MSP1OU1", "MSP1") + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + msg := []byte("TestMessage") + sig, err := id.Sign(msg) + require.NoError(t, err) + + err = id.Verify(msg, sig) + require.NoError(t, err) + + err = id.Verify([]byte("OtherMessage"), sig) + require.Error(t, err) + require.Contains(t, err.Error(), "pseudonym signature invalid: zero-knowledge proof is invalid") + + verMsp, err := setup("testdata/idemix/MSP1Verifier", "MSP1") + require.NoError(t, err) + err = verMsp.Validate(id) + require.NoError(t, err) + _, err = verMsp.GetDefaultSigningIdentity() + require.Error(t, err) + require.Contains(t, err.Error(), "no default signer setup") +} + +func TestSigningBad(t *testing.T) { + msp, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + msg := []byte("TestMessage") + sig := []byte("barf") + + err = id.Verify(msg, sig) + require.Error(t, err) + require.Contains(t, err.Error(), "error unmarshalling signature") +} + +func TestIdentitySerialization(t *testing.T) { + msp, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id, err := getDefaultSigner(msp) + require.NoError(t, err) + + // Test serialization of identities + serializedID, err := id.Serialize() + require.NoError(t, err) + + verID, err := msp.DeserializeIdentity(serializedID) + require.NoError(t, err) + + err = verID.Validate() + require.NoError(t, err) + + err = msp.Validate(verID) + require.NoError(t, err) +} + +func TestIdentitySerializationBad(t *testing.T) { + msp, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + _, err = msp.DeserializeIdentity([]byte("barf")) + require.Error(t, err, "DeserializeIdentity should have failed for bad input") + require.Contains(t, err.Error(), "could not deserialize a SerializedIdentity") +} + +func TestIdentitySerializationWrongMSP(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + msp2, err := setup("testdata/idemix/MSP2OU1", "MSP2OU1") + require.NoError(t, err) + id2, err := getDefaultSigner(msp2) + require.NoError(t, err) + + idBytes, err := id2.Serialize() + require.NoError(t, err) + + _, err = msp1.DeserializeIdentity(idBytes) + require.Error(t, err, "DeserializeIdentity should have failed for ID of other MSP") + require.Contains(t, err.Error(), "expected MSP ID MSP1OU1, received MSP2OU1") +} + +func TestPrincipalIdentity(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + idBytes, err := id1.Serialize() + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalIdentityWrongIdentity(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + msp2, err := setup("testdata/idemix/MSP1OU2", "MSP1OU2") + require.NoError(t, err) + + id2, err := getDefaultSigner(msp2) + require.NoError(t, err) + + idBytes, err := id1.Serialize() + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id2.SatisfiesPrincipal(principal) + require.Error(t, err, "Identity MSP principal for different user should fail") + require.Contains(t, err.Error(), "the identities do not match") + +} + +func TestPrincipalIdentityBadIdentity(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + idBytes := []byte("barf") + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_IDENTITY, + Principal: idBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Identity MSP principal for a bad principal should fail") + require.Contains(t, err.Error(), "the identities do not match") +} + +func TestAnonymityPrincipal(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_ANONYMOUS}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestAnonymityPrincipalBad(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_NOMINAL}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Idemix identity is anonymous and should not pass NOMINAL anonymity principal") + require.Contains(t, err.Error(), "principal is nominal, but idemix MSP is anonymous") +} + +func TestAnonymityPrincipalV11(t *testing.T) { + msp1, err := setupWithVersion("testdata/idemix/MSP1OU1", "MSP1OU1", MSPv1_1) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPIdentityAnonymity{AnonymityType: msp.MSPIdentityAnonymity_NOMINAL}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ANONYMITY, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err) + require.Contains(t, err.Error(), "Anonymity MSP Principals are unsupported in MSPv1_1") +} + +func TestIdemixIsWellFormed(t *testing.T) { + idemixMSP, err := setup("testdata/idemix/MSP1OU1", "TestName") + require.NoError(t, err) + + id, err := getDefaultSigner(idemixMSP) + require.NoError(t, err) + rawId, err := id.Serialize() + require.NoError(t, err) + sId := &msp.SerializedIdentity{} + err = proto.Unmarshal(rawId, sId) + require.NoError(t, err) + err = idemixMSP.IsWellFormed(sId) + require.NoError(t, err) + // Corrupt the identity bytes + sId.IdBytes = append(sId.IdBytes, 1) + err = idemixMSP.IsWellFormed(sId) + require.Error(t, err) + require.Contains(t, err.Error(), "not an idemix identity") +} + +func TestPrincipalOU(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalOUWrongOU(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: "DifferentOU", + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for user of different OU") + require.Contains(t, err.Error(), "user is not part of the desired organizational unit") + +} + +func TestPrincipalOUWrongMSP(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: "OU1", + MspIdentifier: "OtherMSP", + CertifiersIdentifier: nil, + } + bytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for user of different MSP") + require.Contains(t, err.Error(), "the identity is a member of a different MSP") + +} + +func TestPrincipalOUBad(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + bytes := []byte("barf") + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: bytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "OU MSP principal should have failed for a bad OU principal") + require.Contains(t, err.Error(), "could not unmarshal OU from principal") +} + +func TestPrincipalRoleMember(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) + + // Member should also satisfy client + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_CLIENT, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal = &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalRoleAdmin(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1Admin", "MSP1OU1Admin") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + // Admin should also satisfy member + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal = &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.NoError(t, err) +} + +func TestPrincipalRoleNotPeer(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1Admin", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_PEER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Admin should not satisfy PEER principal") + require.Contains(t, err.Error(), "idemixmsp only supports client use, so it cannot satisfy an MSPRole PEER principal") +} + +func TestPrincipalRoleNotAdmin(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Member should not satisfy Admin principal") + require.Contains(t, err.Error(), "user is not an admin") +} + +func TestPrincipalRoleWrongMSP(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: "OtherMSP"}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Role MSP principal should have failed for user of different MSP") + require.Contains(t, err.Error(), "the identity is a member of a different MSP") +} + +func TestPrincipalRoleBadRole(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + // Make principal for nonexisting role 1234 + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: 1234, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Role MSP principal should have failed for a bad Role") + require.Contains(t, err.Error(), "invalid MSP role type") +} + +func TestPrincipalBad(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principal := &msp.MSPPrincipal{ + PrincipalClassification: 1234, + Principal: nil} + + err = id1.SatisfiesPrincipal(principal) + require.Error(t, err, "Principal with bad Classification should fail") + require.Contains(t, err.Error(), "invalid principal type") +} + +func TestPrincipalCombined(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.NoError(t, err) +} + +func TestPrincipalCombinedBad(t *testing.T) { + msp1, err := setup("testdata/idemix/MSP1OU1", "MSP1OU1") + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + // create combined principal requiring membership of OU1 in MSP1 and requiring admin role + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_ADMIN, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.Error(t, err, "non-admin member of OU1 in MSP1 should not satisfy principal admin and OU1 in MSP1") + require.Contains(t, err.Error(), "user is not an admin") +} + +func TestPrincipalCombinedV11(t *testing.T) { + msp1, err := setupWithVersion("testdata/idemix/MSP1OU1", "MSP1OU1", MSPv1_1) + require.NoError(t, err) + + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + ou := &msp.OrganizationUnit{ + OrganizationalUnitIdentifier: id1.GetOrganizationalUnits()[0].OrganizationalUnitIdentifier, + MspIdentifier: id1.GetMSPIdentifier(), + CertifiersIdentifier: nil, + } + principalBytes, err := proto.Marshal(ou) + require.NoError(t, err) + + principalOU := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ORGANIZATION_UNIT, + Principal: principalBytes} + + principalBytes, err = proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + + principals := []*msp.MSPPrincipal{principalOU, principalRole} + + combinedPrincipal := &msp.CombinedPrincipal{Principals: principals} + combinedPrincipalBytes, err := proto.Marshal(combinedPrincipal) + + require.NoError(t, err) + + principalsCombined := &msp.MSPPrincipal{PrincipalClassification: msp.MSPPrincipal_COMBINED, Principal: combinedPrincipalBytes} + + err = id1.SatisfiesPrincipal(principalsCombined) + require.Error(t, err) + require.Contains(t, err.Error(), "Combined MSP Principals are unsupported in MSPv1_1") +} + +func TestRoleClientV11(t *testing.T) { + msp1, err := setupWithVersion("testdata/idemix/MSP1OU1", "MSP1OU1", MSPv1_1) + require.NoError(t, err) + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_CLIENT, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + err = id1.SatisfiesPrincipal(principalRole) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid MSP role type") +} + +func TestRolePeerV11(t *testing.T) { + msp1, err := setupWithVersion("testdata/idemix/MSP1OU1", "MSP1OU1", MSPv1_1) + require.NoError(t, err) + id1, err := getDefaultSigner(msp1) + require.NoError(t, err) + + principalBytes, err := proto.Marshal(&msp.MSPRole{Role: msp.MSPRole_PEER, MspIdentifier: id1.GetMSPIdentifier()}) + require.NoError(t, err) + principalRole := &msp.MSPPrincipal{ + PrincipalClassification: msp.MSPPrincipal_ROLE, + Principal: principalBytes} + err = id1.SatisfiesPrincipal(principalRole) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid MSP role type") +} diff --git a/v2/msp.go b/v2/msp.go new file mode 100644 index 0000000..daad00c --- /dev/null +++ b/v2/msp.go @@ -0,0 +1,219 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "time" + + "github.com/hyperledger/fabric-protos-go-apiv2/msp" +) + +// IdentityDeserializer is implemented by both MSPManger and MSP +type IdentityDeserializer interface { + // DeserializeIdentity deserializes an identity. + // Deserialization will fail if the identity is associated to + // an msp that is different from this one that is performing + // the deserialization. + DeserializeIdentity(serializedIdentity []byte) (Identity, error) + + // IsWellFormed checks if the given identity can be deserialized into its provider-specific form + IsWellFormed(identity *msp.SerializedIdentity) error +} + +// Membership service provider APIs for Hyperledger Fabric: +// +// By "membership service provider" we refer to an abstract component of the +// system that would provide (anonymous) credentials to clients, and peers for +// them to participate in Hyperledger/fabric network. Clients use these +// credentials to authenticate their transactions, and peers use these credentials +// to authenticate transaction processing results (endorsements). While +// strongly connected to the transaction processing components of the systems, +// this interface aims to have membership services components defined, in such +// a way such that alternate implementations of this can be smoothly plugged in +// without modifying the core of transaction processing components of the system. +// +// This file includes Membership service provider interface that covers the +// needs of a peer membership service provider interface. + +// MSPManager is an interface defining a manager of one or more MSPs. This +// essentially acts as a mediator to MSP calls and routes MSP related calls +// to the appropriate MSP. +// This object is immutable, it is initialized once and never changed. +type MSPManager interface { + + // IdentityDeserializer interface needs to be implemented by MSPManager + IdentityDeserializer + + // Setup the MSP manager instance according to configuration information + Setup(msps []MSP) error + + // GetMSPs Provides a list of Membership Service providers + GetMSPs() (map[string]MSP, error) +} + +// MSP is the minimal Membership Service Provider Interface to be implemented +// to accommodate peer functionality +type MSP interface { + + // IdentityDeserializer interface needs to be implemented by MSP + IdentityDeserializer + + // Setup the MSP instance according to configuration information + Setup(config *msp.MSPConfig) error + + // GetVersion returns the version of this MSP + GetVersion() MSPVersion + + // GetType returns the provider type + GetType() ProviderType + + // GetIdentifier returns the provider identifier + GetIdentifier() (string, error) + + // GetDefaultSigningIdentity returns the default signing identity + GetDefaultSigningIdentity() (SigningIdentity, error) + + // GetTLSRootCerts returns the TLS root certificates for this MSP + GetTLSRootCerts() [][]byte + + // GetTLSIntermediateCerts returns the TLS intermediate root certificates for this MSP + GetTLSIntermediateCerts() [][]byte + + // Validate checks whether the supplied identity is valid + Validate(id Identity) error + + // SatisfiesPrincipal checks whether the identity matches + // the description supplied in MSPPrincipal. The check may + // involve a byte-by-byte comparison (if the principal is + // a serialized identity) or may require MSP validation + SatisfiesPrincipal(id Identity, principal *msp.MSPPrincipal) error +} + +// OUIdentifier represents an organizational unit and +// its related chain of trust identifier. +type OUIdentifier struct { + // CertifiersIdentifier is the hash of certificates chain of trust + // related to this organizational unit + CertifiersIdentifier []byte + // OrganizationUnitIdentifier defines the organizational unit under the + // MSP identified with MSPIdentifier + OrganizationalUnitIdentifier string +} + +// From this point on, there are interfaces that are shared within the peer and client API +// of the membership service provider. + +// Identity interface defining operations associated to a "certificate". +// That is, the public part of the identity could be thought to be a certificate, +// and offers solely signature verification capabilities. This is to be used +// at the peer side when verifying certificates that transactions are signed +// with, and verifying signatures that correspond to these certificates./// +type Identity interface { + + // ExpiresAt returns the time at which the Identity expires. + // If the returned time is the zero value, it implies + // the Identity does not expire, or that its expiration + // time is unknown + ExpiresAt() time.Time + + // GetIdentifier returns the identifier of that identity + GetIdentifier() *IdentityIdentifier + + // GetMSPIdentifier returns the MSP Id for this instance + GetMSPIdentifier() string + + // Validate uses the rules that govern this identity to validate it. + // E.g., if it is a fabric TCert implemented as identity, validate + // will check the TCert signature against the assumed root certificate + // authority. + Validate() error + + // GetOrganizationalUnits returns zero or more organization units or + // divisions this identity is related to as long as this is public + // information. Certain MSP implementations may use attributes + // that are publicly associated to this identity, or the identifier of + // the root certificate authority that has provided signatures on this + // certificate. + // Examples: + // - if the identity is an x.509 certificate, this function returns one + // or more string which is encoded in the Subject's Distinguished Name + // of the type OU + // TODO: For X.509 based identities, check if we need a dedicated type + // for OU where the Certificate OU is properly namespaced by the + // signer's identity + GetOrganizationalUnits() []*OUIdentifier + + // Anonymous returns true if this is an anonymous identity, false otherwise + Anonymous() bool + + // Verify a signature over some message using this identity as reference + Verify(msg []byte, sig []byte) error + + // Serialize converts an identity to bytes + Serialize() ([]byte, error) + + // SatisfiesPrincipal checks whether this instance matches + // the description supplied in MSPPrincipal. The check may + // involve a byte-by-byte comparison (if the principal is + // a serialized identity) or may require MSP validation + SatisfiesPrincipal(principal *msp.MSPPrincipal) error +} + +// SigningIdentity is an extension of Identity to cover signing capabilities. +// E.g., signing identity should be requested in the case of a client who wishes +// to sign transactions, or fabric endorser who wishes to sign proposal +// processing outcomes. +type SigningIdentity interface { + + // Extends Identity + Identity + + // Sign the message + Sign(msg []byte) ([]byte, error) + + // GetPublicVersion returns the public parts of this identity + GetPublicVersion() Identity +} + +// IdentityIdentifier is a holder for the identifier of a specific +// identity, naturally namespaced, by its provider identifier. +type IdentityIdentifier struct { + + // The identifier of the associated membership service provider + Mspid string + + // The identifier for an identity within a provider + Id string +} + +// ProviderType indicates the type of an identity provider +type ProviderType int + +// The ProviderType of a member relative to the member API +const ( + FABRIC ProviderType = iota // MSP is of FABRIC type + IDEMIX // MSP is of IDEMIX type + IDEMIX_ARIES // MSP is of IDEMIX_ARIES type + OTHER // MSP is of OTHER TYPE + + // NOTE: as new types are added to this set, + // the mspTypes map below must be extended +) + +var mspTypeStrings = map[ProviderType]string{ + FABRIC: "bccsp", + IDEMIX: "idemix", +} + +// ProviderTypeToString returns a string that represents the ProviderType integer +func ProviderTypeToString(id ProviderType) string { + if res, found := mspTypeStrings[id]; found { + return res + } + + return "" +} diff --git a/v2/testdata/aries/EidRH/ca/IssuerPublicKey b/v2/testdata/aries/EidRH/ca/IssuerPublicKey new file mode 100644 index 0000000..ab9846f --- /dev/null +++ b/v2/testdata/aries/EidRH/ca/IssuerPublicKey @@ -0,0 +1 @@ +{lå]5.y䢄9Tf/U"{mQ1XW_o -Tb0][p+ D[ |;kDq=󡾺)CaF:d7 \ No newline at end of file diff --git a/v2/testdata/aries/EidRH/ca/IssuerSecretKey b/v2/testdata/aries/EidRH/ca/IssuerSecretKey new file mode 100644 index 0000000..f600297 --- /dev/null +++ b/v2/testdata/aries/EidRH/ca/IssuerSecretKey @@ -0,0 +1 @@ +@8ii8wrœH(T \ No newline at end of file diff --git a/v2/testdata/aries/EidRH/ca/RevocationKey b/v2/testdata/aries/EidRH/ca/RevocationKey new file mode 100644 index 0000000..41f71c5 --- /dev/null +++ b/v2/testdata/aries/EidRH/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDCpSBsf9HaNF0c1M82MFJUas8l4G52sRfCKjDFBhHmHAv0A9eUYLW0b ++54j8HTW5WSgBwYFK4EEACKhZANiAAT629qA56MP79wqYIeguOmBKyy2OuYGikkY +wI1/qdiAOt2ZSVd/jhFK8XrwRpFpPY3ECWtcA4jr99FOhhDE+FPXiP9p7rNkSWo2 +2l76hvF7GYWksWWL/R3uyvRU70lPpVE= +-----END PRIVATE KEY----- diff --git a/v2/testdata/aries/EidRH/msp/IssuerPublicKey b/v2/testdata/aries/EidRH/msp/IssuerPublicKey new file mode 100644 index 0000000..ab9846f --- /dev/null +++ b/v2/testdata/aries/EidRH/msp/IssuerPublicKey @@ -0,0 +1 @@ +{lå]5.y䢄9Tf/U"{mQ1XW_o -Tb0][p+ D[ |;kDq=󡾺)CaF:d7 \ No newline at end of file diff --git a/v2/testdata/aries/EidRH/msp/RevocationPublicKey b/v2/testdata/aries/EidRH/msp/RevocationPublicKey new file mode 100644 index 0000000..06c41f6 --- /dev/null +++ b/v2/testdata/aries/EidRH/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+tvagOejD+/cKmCHoLjpgSsstjrmBopJ +GMCNf6nYgDrdmUlXf44RSvF68EaRaT2NxAlrXAOI6/fRToYQxPhT14j/ae6zZElq +Ntpe+obxexmFpLFli/0d7sr0VO9JT6VR +-----END PUBLIC KEY----- diff --git a/v2/testdata/aries/EidRH/user/SignerConfig b/v2/testdata/aries/EidRH/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..70c80fc3bb77338fbf4468fa5e56260310f93dca GIT binary patch literal 640 zcmd=3#mH5#g~{tflGFP1{d^ydK7K#V!(_5{%Z4JYH5N}!Xp1Dj)7+Etx&3&`p31ED zR9D@VqJ1$QduPA768tfD?%tmzZK2b<`uacS*cA9GUd&{fu=~A{dJgxal|OyfJv%w! zY0lS^hyJbH9joAU(QjSM-@1>T2M-7-Fh7u zzdoD^yCC%VEt~GPD{MU}`LBDP8(rLUDC+g`C+AO35>gOfG~eP+Tagss?NesPigJa!Flq?^8L4?i`8hecsd**3nJGqVnS>573O~?J z2r8VPP`l>xf(~{6)X3}$7iKDKliGSKard4208e9|k3zyzV#A-Vsb!62(XM0iTD0jy zKi5Px4pw#DqXL1)o>X1=hZ;L?lsJ6D_D2GztRwP;-KQ*9c*kz7U(TE zIb*cu(gy8A-7`6X$urh#7Ii+H9MZ8~1QbOkfD#y7u(4;^l&VRYpN&T+M`GM%?^MO7}#u5d2}pq6Dl zCdUFS|NYnYnlZ0n=7&!*_HtkH8;zvJ7uOX(WTo`csPy9V?u;Qi^WBf{;gHH$hQP7Z0~*rCBFm2QyAWWav*i!t{j)1I%E5*PEPuZZ1c z(NvxN`G;=%+jze-W@@KeH_X3~7+Ld5cZ#T}pZ4RMOoj|=Cl|eL?(qgqu%}g;eTEirCfKm8?c0y3${Dj&ymlt%X`=>@` zU$`(+VVl&}TZy~x)CYJP`+O7lh>k6ANsi_s&TNY>mC&dJocpO z$}b0NrY(WoDOHazmUFLRR$IZk`~8)MuoDLr_wHb0JF-A;xyc!$HJ3JMAL^cY+D@ac zb$Rl7wUSAz!}Vsawm0Whk9i}zaMeoo_z&@w`*@ySRWGW|6YM#8_Sh{wQ>nFoXRqqn zdwF@T)2z(Ego`?ptWKV>UbCq4;pC7ujvX3|Qt1Y%Ooj}5R|iMDF|ca6_vF&=umAk* ztzT`ooSZS^Li|Sy|B|gPL9geW(b9OcIVys8QQ literal 0 HcmV?d00001 diff --git a/v2/testdata/aries/MSP1Verifier/ca/IssuerPublicKey b/v2/testdata/aries/MSP1Verifier/ca/IssuerPublicKey new file mode 100644 index 0000000..1776da1 --- /dev/null +++ b/v2/testdata/aries/MSP1Verifier/ca/IssuerPublicKey @@ -0,0 +1 @@ +^b'Sf;gҧ"TϳnOugN% 0(9"EM\ڲ%}KJB*'gF 1S2G \ No newline at end of file diff --git a/v2/testdata/aries/MSP1Verifier/ca/IssuerSecretKey b/v2/testdata/aries/MSP1Verifier/ca/IssuerSecretKey new file mode 100644 index 0000000..cc74c48 --- /dev/null +++ b/v2/testdata/aries/MSP1Verifier/ca/IssuerSecretKey @@ -0,0 +1 @@ +`tiuyEIl"R+ F# \ No newline at end of file diff --git a/v2/testdata/aries/MSP1Verifier/ca/RevocationKey b/v2/testdata/aries/MSP1Verifier/ca/RevocationKey new file mode 100644 index 0000000..bb5aa94 --- /dev/null +++ b/v2/testdata/aries/MSP1Verifier/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDDqLcq4OwqhIlmz/SKasQy7WO7zE8yi911BDJzCMraWWNNV/pF3IMRS +98e654H0ugWgBwYFK4EEACKhZANiAAQcQQsgx/52KDgK4MGwlNTIC5gRaU8cAIEy +yrDiGlymeU7Gjlv6PGD4CFg9hP8CjxBjcWXLGWZB0zZzWeAfyScMYt85n1FAFcgi +my7NoVabVitXOacObbiFjERzpoXGWck= +-----END PRIVATE KEY----- diff --git a/v2/testdata/aries/MSP1Verifier/msp/IssuerPublicKey b/v2/testdata/aries/MSP1Verifier/msp/IssuerPublicKey new file mode 100644 index 0000000..1776da1 --- /dev/null +++ b/v2/testdata/aries/MSP1Verifier/msp/IssuerPublicKey @@ -0,0 +1 @@ +^b'Sf;gҧ"TϳnOugN% 0(9"EM\ڲ%}KJB*'gF 1S2G \ No newline at end of file diff --git a/v2/testdata/aries/MSP1Verifier/msp/RevocationPublicKey b/v2/testdata/aries/MSP1Verifier/msp/RevocationPublicKey new file mode 100644 index 0000000..3689f96 --- /dev/null +++ b/v2/testdata/aries/MSP1Verifier/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEHEELIMf+dig4CuDBsJTUyAuYEWlPHACB +Msqw4hpcpnlOxo5b+jxg+AhYPYT/Ao8QY3FlyxlmQdM2c1ngH8knDGLfOZ9RQBXI +IpsuzaFWm1YrVzmnDm24hYxEc6aFxlnJ +-----END PUBLIC KEY----- diff --git a/v2/testdata/aries/MSP2OU1eid1/ca/IssuerPublicKey b/v2/testdata/aries/MSP2OU1eid1/ca/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..6c31ee77256a5dba561831528c0a8d3bcb87b74f GIT binary patch literal 96 zcmV-m0H6P;aKxdLuN9Be<1BF;nV>6^8~`|%o=9{o3nMx`1nkhvqzGd9eO-BmC4&%? z6@)eht}A;E4EgLRmrAi=uUYt6>8c04#I C9xPY@ literal 0 HcmV?d00001 diff --git a/v2/testdata/aries/MSP2OU1eid1/ca/IssuerSecretKey b/v2/testdata/aries/MSP2OU1eid1/ca/IssuerSecretKey new file mode 100644 index 0000000000000000000000000000000000000000..fb132b47af2ae3d6508bb0ca98b3f7f0fda22dbf GIT binary patch literal 32 ocmeZ%dm+@Ts_}Ura@&Et; literal 0 HcmV?d00001 diff --git a/v2/testdata/aries/MSP2OU1eid1/ca/RevocationKey b/v2/testdata/aries/MSP2OU1eid1/ca/RevocationKey new file mode 100644 index 0000000..f0aebf2 --- /dev/null +++ b/v2/testdata/aries/MSP2OU1eid1/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDBto9hCI4MmJgsT0/6voazPGORE2OmUC26N95P0zMDkkMMyP4G1baSt +549c+0LCkC+gBwYFK4EEACKhZANiAAQyLD9/F8s2EjhyDeoHisfsEwbYQpldtnCH +dsa1ZP5KqC7SrwQlf9qYb6wv/bRCEmisqt6JRhsD8N9esvSVSWvoYpDKjOa+f7le +ARyQVCfz7Jr5dPiLVaJVNgQumi/RfD0= +-----END PRIVATE KEY----- diff --git a/v2/testdata/aries/MSP2OU1eid1/msp/IssuerPublicKey b/v2/testdata/aries/MSP2OU1eid1/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..6c31ee77256a5dba561831528c0a8d3bcb87b74f GIT binary patch literal 96 zcmV-m0H6P;aKxdLuN9Be<1BF;nV>6^8~`|%o=9{o3nMx`1nkhvqzGd9eO-BmC4&%? z6@)eht}A;E4EgLRmrAi=uUYt6>8c04#I C9xPY@ literal 0 HcmV?d00001 diff --git a/v2/testdata/aries/MSP2OU1eid1/msp/RevocationPublicKey b/v2/testdata/aries/MSP2OU1eid1/msp/RevocationPublicKey new file mode 100644 index 0000000..083e5cc --- /dev/null +++ b/v2/testdata/aries/MSP2OU1eid1/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMiw/fxfLNhI4cg3qB4rH7BMG2EKZXbZw +h3bGtWT+Sqgu0q8EJX/amG+sL/20QhJorKreiUYbA/DfXrL0lUlr6GKQyozmvn+5 +XgEckFQn8+ya+XT4i1WiVTYELpov0Xw9 +-----END PUBLIC KEY----- diff --git a/v2/testdata/aries/MSP2OU1eid1/user/SignerConfig b/v2/testdata/aries/MSP2OU1eid1/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..65d25e3b1d6cafdf07d831f7b0c3126e7426eb43 GIT binary patch literal 603 zcmd=3#mH4K(cx}zl8QXbr71lvpSO!ku30bh{oQsahg&Dwk`^9nIg**-vi{Jfr2=eH zj$h{7K6Ho4&}!|PXM5xtz5d^n@6!C@-?XDq=#Ryb&&fU9f6i$?DX5KIZU10f5j)?u zts!y;JabLr-P*bPOAk-n!&BZ^cvncla^VEtIR9G?X$mVHCqKQuG^oU)phza_MC*oi zg$uY9nS~S>@B>C61<}x{M#bXm59yo?uD)Kyk-Yq(XmF;@@~n4zPO}+bi@qkLkZE$@ z5VvLd6>YOHGwIOxVv^cd8opm#qIRa8^;*!wyIn#Gs{2^OxyyDJyf{;gHH$hQP7Z0~*rCBFm1dB_WMGh0#=p|lzx&-U%_@&ueOa?aotNBV!EAv%M;C%ki%92m$ llrHPV>wVQyq>WfDXR zApI@q{PH4~(~Yxp<146uv>mY<^-9Uj04s+!X8AKzXA&TL>E9i43LVD#W+tMngR^v1 z$U^~R237ML!VpLk_7cw;L<%7AcB9y8g0Yxi_$P94`MbcwBQ=YYg?!Q9_rqv?KxD-d zAO*i)A^r21_A{pT=L4DOJ~L3Z>Ua^;uRbIB7D^b!dLl#$Ahi@U^Kx9K=1$nZ28;!i z5N)04O5B7NwrW0`Tmxz&UJ@X9IO5eaOS$rOtB07h+)3yT3eHk!jIjn1AhBM$A45JO zL<%4rAD!vnsFw;1q7`vQ!y-{QPh|~57v9GD_xA=OL<%4f6fZx4>0N=`s)};U zal}8TQao`+l|0wgmX{baTf7AlAYsCi_Bi6}8+mQi&w8Pz%f`^T{NM zAk88m|CbEh?G|U>HnIk$mD}_{)~m31r;s3Iw|QEMe=@0Sn}oy( zDQHkC91}BQ8Z}$eX)MIuBbplt5+G+t81hA|d8t(wYc-?l?A8UmVXD7$Df0~qfe<0X zHu^e53Lqbs!-|qI5US^v>!KMYGL=PRmskAmkjF4PF%1rC^?ni{Lc~=`Vh?pECd&bQ zsD(Vf)mov-Tn_6zfhAr>^4a(r{gXVH;i`n7(^vq4zbT9QqiV2nn`8xUcd^`XE literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/EidRH/ca/IssuerSecretKey b/v2/testdata/idemix/EidRH/ca/IssuerSecretKey new file mode 100644 index 0000000..e169f4a --- /dev/null +++ b/v2/testdata/idemix/EidRH/ca/IssuerSecretKey @@ -0,0 +1 @@ +9wM(dVCyȸk"$. \ No newline at end of file diff --git a/v2/testdata/idemix/EidRH/ca/RevocationKey b/v2/testdata/idemix/EidRH/ca/RevocationKey new file mode 100644 index 0000000..d56b498 --- /dev/null +++ b/v2/testdata/idemix/EidRH/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDA5veMN5FDY2j29d5qdIYnhAuRwSjMPMgDuML5hroKa27H75qinmDAV +N+PY8VCzqCagBwYFK4EEACKhZANiAARRhTsyo0HH2D5TYCfZvLSqsUTKCtCkpSsm +nDzHPFbB3vaqb1TFCgwmNaTGnZinVqGy/2e83SCMc4eXNdNhIOuc56yBlqBPV9Cx +B25y/qT3XJS/tkQtjhz4LZ7S1owerMs= +-----END PRIVATE KEY----- diff --git a/v2/testdata/idemix/EidRH/msp/IssuerPublicKey b/v2/testdata/idemix/EidRH/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..92a0a476006da6c36c90c6193ea9af05a180ed9a GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zApI@q{PH4~(~Yxp<146uv>mY<^-9Uj04s+!X8AKzXA&TL>E9i43LVD#W+tMngR^v1 z$U^~R237ML!VpLk_7cw;L<%7AcB9y8g0Yxi_$P94`MbcwBQ=YYg?!Q9_rqv?KxD-d zAO*i)A^r21_A{pT=L4DOJ~L3Z>Ua^;uRbIB7D^b!dLl#$Ahi@U^Kx9K=1$nZ28;!i z5N)04O5B7NwrW0`Tmxz&UJ@X9IO5eaOS$rOtB07h+)3yT3eHk!jIjn1AhBM$A45JO zL<%4rAD!vnsFw;1q7`vQ!y-{QPh|~57v9GD_xA=OL<%4f6fZx4>0N=`s)};U zal}8TQao`+l|0wgmX{baTf7AlAYsCi_Bi6}8+mQi&w8Pz%f`^T{NM zAk88m|CbEh?G|U>HnIk$mD}_{)~m31r;s3Iw|QEMe=@0Sn}oy( zDQHkC91}BQ8Z}$eX)MIuBbplt5+G+t81hA|d8t(wYc-?l?A8UmVXD7$Df0~qfe<0X zHu^e53Lqbs!-|qI5US^v>!KMYGL=PRmskAmkjF4PF%1rC^?ni{Lc~=`Vh?pECd&bQ zsD(Vf)mov-Tn_6zfhAr>^4a(r{gXVH;i`n7(^vq4zbT9QqiV2nn`8xUcd^`XE literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/EidRH/msp/RevocationPublicKey b/v2/testdata/idemix/EidRH/msp/RevocationPublicKey new file mode 100644 index 0000000..21fcc25 --- /dev/null +++ b/v2/testdata/idemix/EidRH/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUYU7MqNBx9g+U2An2by0qrFEygrQpKUr +Jpw8xzxWwd72qm9UxQoMJjWkxp2Yp1ahsv9nvN0gjHOHlzXTYSDrnOesgZagT1fQ +sQducv6k91yUv7ZELY4c+C2e0taMHqzL +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/EidRH/user/SignerConfig b/v2/testdata/idemix/EidRH/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..8afb3f682b937acdb038bd352067abc3e56cf0c1 GIT binary patch literal 677 zcmdcxIZALc@dNFS}DS zPhOa&RXBs+>a}XwrmtVl23(51+O)4;yhBO|Xo~%6YfZ69YD*0tyqp(PB)_Hf^1YY! z@1jDaLoIg5WJVqnQrLPu@sLl&Ki5*rd12e9pVzN$3M%yZwW>Vz{I4C4uB+)vDX6eI zozMEn#!~V~xcJuU2KK{8?>%5l@>n7KgQH;iy*frEg|45xM`G9Yz4VDX_1I`}<>HJM z=J`8*FFHBhD^Md){X&?QLh<>`HLhH2b0=J5+;(ke|3Pj`n;8waDw9*beO8sb5?7$5 zzA4|fqB1$6#SMYgco-m2)P~g@R<1x;hidXD;~99FzvjQ z`PO7{U6!_#LV<$Y^P^6SHi?A#SVJD~x!iGR zg44TYo|<)==6Cz-YiN?1vH5{eqoCJXmf2G22B}O2oQ@_BmzJOY@uxzH+h%IyKf?>t zY<53?-^1yjWa1(8XwS2w3XWl#rr#PLgj9zazPiX{$nbIQ60xPmADxzLRTGapRkGvF zxAz`T`gAsyI~|$Cl=$bTF?+B2kz*PO57!h-GOIr{+e!o&5JjnF`N@eTnfZCT8Hsr* GIjI0{E-bwO literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/MSP1OU1/ca/IssuerPublicKey b/v2/testdata/idemix/MSP1OU1/ca/IssuerPublicKey new file mode 100644 index 0000000..034daa5 --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1/ca/IssuerPublicKey @@ -0,0 +1,16 @@ + +OU +Role + EnrollmentID +RevocationHandleD + ^٪hĉl<3t%(NP 5x|_}|M΂38uUX˷D + '+{S9! x^k좃9NXO{?% @n GڣHD8|٘p$6!6"D + !sdW40bʸTD-+(mX ʺ[Ҋεx ˛[T"D + O(1qJ)Ji,\a e%s3 ]#%"D + \nm;s=asmR&Wm#j ,.lTwjWpH6hg]eAT~"D + oE6‘۔<$^bbE% HؙgּI]nT ǃ'܆٘* + U~;lkE0S&Lj̗#@9PVN >ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU1/ca/IssuerSecretKey b/v2/testdata/idemix/MSP1OU1/ca/IssuerSecretKey new file mode 100644 index 0000000..10b591d --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1/ca/IssuerSecretKey @@ -0,0 +1 @@ +d{ҟ \fRQ[ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU1/msp/RevocationPublicKey b/v2/testdata/idemix/MSP1OU1/msp/RevocationPublicKey new file mode 100644 index 0000000..a0fc2af --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE76UE50n31TB34E3tEHi9vyaLXEJIpxF6 +Ur1eWKRIpZ7Pyi55fHg93nM2kKwglbX6LLRIl0nzMLhgvwJpIlVh+REM63cNj9D0 +80OaN8HetLRG7Hpj7ipR3Q4VjZ0x22ZC +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/MSP1OU1/user/SignerConfig b/v2/testdata/idemix/MSP1OU1/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..f496457a543ae0fae3d7c9c95a79d750231fa8b7 GIT binary patch literal 644 zcmd1_H+NlnbX-Q=H@{k@B~8$9_r2Snq?r9f4HX!**mH{u4D(A3jlM7nbue-% z{NphW*m5#QK_iSmyhdZ9)j65qSwj0;Y%ZyZZRO) zzmfuL$m2biI}S~7dbi9|vu@M;Zl8S(O;R&9KM-mZ^jga@TPockmC2Byo6)l}=c-`N zs&%!tM(6CSYh%wArQWGu@ypzN_CEh}&mV7_5U%2R?}2SFr`%5CibYHYmaIa?>bmc0 umYq~QBP6!R>X+nagCLvR@>-rXf|K6KUw-P&_BF*sIPHpZ&8N$^A{hXT;Rs*= literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerPublicKey b/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerPublicKey new file mode 100644 index 0000000..034daa5 --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerPublicKey @@ -0,0 +1,16 @@ + +OU +Role + EnrollmentID +RevocationHandleD + ^٪hĉl<3t%(NP 5x|_}|M΂38uUX˷D + '+{S9! x^k좃9NXO{?% @n GڣHD8|٘p$6!6"D + !sdW40bʸTD-+(mX ʺ[Ҋεx ˛[T"D + O(1qJ)Ji,\a e%s3 ]#%"D + \nm;s=asmR&Wm#j ,.lTwjWpH6hg]eAT~"D + oE6‘۔<$^bbE% HؙgּI]nT ǃ'܆٘* + U~;lkE0S&Lj̗#@9PVN >ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerSecretKey b/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerSecretKey new file mode 100644 index 0000000..10b591d --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1Admin/ca/IssuerSecretKey @@ -0,0 +1 @@ +d{ҟ \fRQ[ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU1Admin/msp/RevocationPublicKey b/v2/testdata/idemix/MSP1OU1Admin/msp/RevocationPublicKey new file mode 100644 index 0000000..a0fc2af --- /dev/null +++ b/v2/testdata/idemix/MSP1OU1Admin/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE76UE50n31TB34E3tEHi9vyaLXEJIpxF6 +Ur1eWKRIpZ7Pyi55fHg93nM2kKwglbX6LLRIl0nzMLhgvwJpIlVh+REM63cNj9D0 +80OaN8HetLRG7Hpj7ipR3Q4VjZ0x22ZC +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/MSP1OU1Admin/user/SignerConfig b/v2/testdata/idemix/MSP1OU1Admin/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..767b1131585cd2362665b12da1d6dc70ea61ff25 GIT binary patch literal 650 zcmdQtj8_WwAy(dG~RNbWs>;5Ytir0)doA(S?6>JDLgsz@Q|zT zhHmFc4c&k91LS|i_@?GB?mef_^5Mx{?$29=fTjqkZ?>MZ{@e6U-gBX=gDUKk{XJ42 z6@Hqv(aEu9itb%OAq8cJMddR;DG0^AVSLO}cD;O~$sC5Al|n}hWR<JU|A;!zvpaqDzS9MsMQipcDeyOo#*{2QRNS4pkwrI13d+(~Q4 zA61u+a@5p3oZdWNFmh?-rl@uQ-nj;^pU7p=B*p9>YN)`Z#gkiHV3=QO=$Mk5nP>Es zQK*BFOW_}naln?7ISLwK{NXhk6Rpn41kV!M-(qt~O>A4|fqB1$6#SMYgco-m2)P~g z@R<1x;hidXD;~99FzvjQ`PO7{U6!_#LV<$Y^P^6SHi?A#SVJD~x!iGRg44TYo|<)==6Cz-YiN?1vH5{eqoCJXmf2Dn25C%&46TR% zS6gS&rZ>9r(Pe=^Et6fQ zC$?qe8-Cvwab?QYe~)b*I~LsOWhsB{Ju!ZE1Ka;k3J>ddR9I-AvQEsFt~xp$0CӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU2/ca/IssuerSecretKey b/v2/testdata/idemix/MSP1OU2/ca/IssuerSecretKey new file mode 100644 index 0000000..10b591d --- /dev/null +++ b/v2/testdata/idemix/MSP1OU2/ca/IssuerSecretKey @@ -0,0 +1 @@ +d{ҟ \fRQ[ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1OU2/msp/RevocationPublicKey b/v2/testdata/idemix/MSP1OU2/msp/RevocationPublicKey new file mode 100644 index 0000000..a0fc2af --- /dev/null +++ b/v2/testdata/idemix/MSP1OU2/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE76UE50n31TB34E3tEHi9vyaLXEJIpxF6 +Ur1eWKRIpZ7Pyi55fHg93nM2kKwglbX6LLRIl0nzMLhgvwJpIlVh+REM63cNj9D0 +80OaN8HetLRG7Hpj7ipR3Q4VjZ0x22ZC +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/MSP1OU2/user/SignerConfig b/v2/testdata/idemix/MSP1OU2/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..7b2c560afac1d62c00f3933e0d0cdbe22e760c94 GIT binary patch literal 644 zcmd=3o|xZ`ys@N=w@_B&q3YkD_oc#Dx2f+HQdqh%@%Q5X zl$}?iEZ+-r4ccR~;10R;!S_mw)_U^CDuK*S$1<~#mb$>!m#+`Y7uV3o<%jBQF zJ`OBlnwL{zMN?Kb)c9&C%o9va{-w7$&_re9ky}3ncQ-8hov$~u;YIM7ZGx|D_%3NF zFyIGFS_*z|nfA+MF@5UK7rErC+laGWG4wpG5d!aDKKfV=N1Ak--6wU%YJRJuVblOe;x-m=xVzWiBT zz1(6(r_++zrQxP*x+O>R-(OBU=pfS=&2V33A^$#Wk{nzPx1rXJ5|z@xCy=&pg34w`V#4U!4dl literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/MSP1Verifier/ca/IssuerPublicKey b/v2/testdata/idemix/MSP1Verifier/ca/IssuerPublicKey new file mode 100644 index 0000000..034daa5 --- /dev/null +++ b/v2/testdata/idemix/MSP1Verifier/ca/IssuerPublicKey @@ -0,0 +1,16 @@ + +OU +Role + EnrollmentID +RevocationHandleD + ^٪hĉl<3t%(NP 5x|_}|M΂38uUX˷D + '+{S9! x^k좃9NXO{?% @n GڣHD8|٘p$6!6"D + !sdW40bʸTD-+(mX ʺ[Ҋεx ˛[T"D + O(1qJ)Ji,\a e%s3 ]#%"D + \nm;s=asmR&Wm#j ,.lTwjWpH6hg]eAT~"D + oE6‘۔<$^bbE% HؙgּI]nT ǃ'܆٘* + U~;lkE0S&Lj̗#@9PVN >ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1Verifier/ca/IssuerSecretKey b/v2/testdata/idemix/MSP1Verifier/ca/IssuerSecretKey new file mode 100644 index 0000000..10b591d --- /dev/null +++ b/v2/testdata/idemix/MSP1Verifier/ca/IssuerSecretKey @@ -0,0 +1 @@ +d{ҟ \fRQ[ӠNHh67fJ 9EZɳ1z*B# N" !9uLNH +y2D + i mLB ^Y~D.D HRxIk%>orV7?YL:D + ]Fq#A3ITgGfK\x + iՉ~o/P։ >ˆPvB 3}x]RJP@DJ ;lDNyxEbtچR Z1̶V0o{܉֫ \ No newline at end of file diff --git a/v2/testdata/idemix/MSP1Verifier/msp/RevocationPublicKey b/v2/testdata/idemix/MSP1Verifier/msp/RevocationPublicKey new file mode 100644 index 0000000..a0fc2af --- /dev/null +++ b/v2/testdata/idemix/MSP1Verifier/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE76UE50n31TB34E3tEHi9vyaLXEJIpxF6 +Ur1eWKRIpZ7Pyi55fHg93nM2kKwglbX6LLRIl0nzMLhgvwJpIlVh+REM63cNj9D0 +80OaN8HetLRG7Hpj7ipR3Q4VjZ0x22ZC +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/MSP2OU1/ca/IssuerPublicKey b/v2/testdata/idemix/MSP2OU1/ca/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..cb904d7580f68ecaf0d66b9e7278dec5a3a7c777 GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zASOz)MNsm6M{u?b3mIG+lCitf4`l-zH6vzb;;k0t&JrN=oLnp+j@5<}^x^F3(HoXs zB+qv>^gJ0-BAtsQajS9~L<%5<5@Mia>k@fFgyxj`Z+5tL(Ixm3j`Cfx^OJ!s(`0fI zAQgH;gE@eeP8&c#gYoMODN6jN`U4)Gb*KT>ov3a*MIuBBAU++ua9l`dV3?zLEmBcN z8!rOp_`=V;_KX%?bE_Z)aS|ZX7cKuv(KqraeJ#bkFpJV>JfD;F!I;MD8_|AHwvx0W zL<%54M9?gz_#@{2Hvbs|oSk=zcp~8wd7q@fHkMG^HRBf&An?D8c8)t+My?d0!KTl@ zVWIDT`?HaEav3dLgAVI^IU+;~AYPx{If6RZM#r@*8)fsHUPp(0vD|0L!I#4BBUAd^ zy%Hd|Rv3Q5j1^jD49E^BB+_BN6kz1-07hUpe$%a9NYfZ1L<%5gdOVqEm97n1m#@o9 zK9<54ulyi!_^=ztdx6i%CgB8q;SI^^$^F28X%>}@hQ`-km*NGZdL4sKJ1z9@Oj~?xZk!bmcz0& zNKqmnAme1nP}j_;KL!_mk3Pj2U^0kQr9d1BI7sZBg;WzT&zB?CB8C&G{!!C z0Bxb|--+w9yk{^VQw+E$RrB4o5+F&5skk<|t*JB>t~INEU$^}vvOz5rp2T7*)*Yxb2D6mZ_q3v)@=2wTx*yPs6<5 VVE`=$2$6V7ts}y=28u4Dn3AsZhAIF6 literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/MSP2OU1/ca/IssuerSecretKey b/v2/testdata/idemix/MSP2OU1/ca/IssuerSecretKey new file mode 100644 index 0000000..7645123 --- /dev/null +++ b/v2/testdata/idemix/MSP2OU1/ca/IssuerSecretKey @@ -0,0 +1 @@ +Z݇-E_} DEtQIw` \ No newline at end of file diff --git a/v2/testdata/idemix/MSP2OU1/ca/RevocationKey b/v2/testdata/idemix/MSP2OU1/ca/RevocationKey new file mode 100644 index 0000000..fc9280c --- /dev/null +++ b/v2/testdata/idemix/MSP2OU1/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDACDZ/H28Lz9ZkRt9DPS0S2St6myYr0yOXUBh6jXnL6Xd6KtxG9Gvbx +izkRk9WPY0qgBwYFK4EEACKhZANiAAShz6e2n4kjEI/AmkIciyGlVXlrcupnNjSN +3wgtr7WvvtmhmrMSxNRNa+wAuaHKcLwUlRvf/S6xswzdFaWkSOv7b0XQrlpTPZkb +Y7l14ajp4H3Le6dw4D8Wj6PLgDA6EXc= +-----END PRIVATE KEY----- diff --git a/v2/testdata/idemix/MSP2OU1/msp/IssuerPublicKey b/v2/testdata/idemix/MSP2OU1/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..cb904d7580f68ecaf0d66b9e7278dec5a3a7c777 GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zASOz)MNsm6M{u?b3mIG+lCitf4`l-zH6vzb;;k0t&JrN=oLnp+j@5<}^x^F3(HoXs zB+qv>^gJ0-BAtsQajS9~L<%5<5@Mia>k@fFgyxj`Z+5tL(Ixm3j`Cfx^OJ!s(`0fI zAQgH;gE@eeP8&c#gYoMODN6jN`U4)Gb*KT>ov3a*MIuBBAU++ua9l`dV3?zLEmBcN z8!rOp_`=V;_KX%?bE_Z)aS|ZX7cKuv(KqraeJ#bkFpJV>JfD;F!I;MD8_|AHwvx0W zL<%54M9?gz_#@{2Hvbs|oSk=zcp~8wd7q@fHkMG^HRBf&An?D8c8)t+My?d0!KTl@ zVWIDT`?HaEav3dLgAVI^IU+;~AYPx{If6RZM#r@*8)fsHUPp(0vD|0L!I#4BBUAd^ zy%Hd|Rv3Q5j1^jD49E^BB+_BN6kz1-07hUpe$%a9NYfZ1L<%5gdOVqEm97n1m#@o9 zK9<54ulyi!_^=ztdx6i%CgB8q;SI^^$^F28X%>}@hQ`-km*NGZdL4sKJ1z9@Oj~?xZk!bmcz0& zNKqmnAme1nP}j_;KL!_mk3Pj2U^0kQr9d1BI7sZBg;WzT&zB?CB8C&G{!!C z0Bxb|--+w9yk{^VQw+E$RrB4o5+F&5skk<|t*JB>t~INEU$^}vvOz5rp2T7*)*Yxb2D6mZ_q3v)@=2wTx*yPs6<5 VVE`=$2$6V7ts}y=28u4Dn3AsZhAIF6 literal 0 HcmV?d00001 diff --git a/v2/testdata/idemix/MSP2OU1/msp/RevocationPublicKey b/v2/testdata/idemix/MSP2OU1/msp/RevocationPublicKey new file mode 100644 index 0000000..ed47424 --- /dev/null +++ b/v2/testdata/idemix/MSP2OU1/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEoc+ntp+JIxCPwJpCHIshpVV5a3LqZzY0 +jd8ILa+1r77ZoZqzEsTUTWvsALmhynC8FJUb3/0usbMM3RWlpEjr+29F0K5aUz2Z +G2O5deGo6eB9y3uncOA/Fo+jy4AwOhF3 +-----END PUBLIC KEY----- diff --git a/v2/testdata/idemix/MSP2OU1/user/SignerConfig b/v2/testdata/idemix/MSP2OU1/user/SignerConfig new file mode 100644 index 0000000000000000000000000000000000000000..90d9578675d703c1a2caf327e1a22d9ba165ae01 GIT binary patch literal 645 zcmdoISU;j|~S4!cu z!sXunuWy^KeEY!J(cr@r$57DB60>ScAaAwMJ9j2OB?Z0<8d-O<=D4lX?B@TQWhxrP z{Fv`EtJyJy4O|-^%9ccFDa?Gkb4B(?)gJ<8HBUX4OU;%^ zu3`a~WLCJYmz%z)+41Aa(D*ox05!|yiy}5hNHP0|8Y(bpvF8>S80D858hvFH>R{wj z_{U=$u;pZqf<_pBc#XzHt8+5JvxN4y*j!Q*+tztt-ftlVzhw#G#a#zNZbv;lW`09> zr^?-mN9`9(J1=FvHCbGjr7fjUpy2lWsMF%jW#zo3LVk9E?@z0g_9jMnyyV={xbKeo zelJ2X_Z!E&C$5`B)hPqVhp|1ESuVoT8GqAJPbe3iXR@60VMT=el304seBd;kCd literal 0 HcmV?d00001