diff --git a/node/src/main/scala/io/iohk/atala/prism/node/NodeExplorerGrpcServiceImpl.scala b/node/src/main/scala/io/iohk/atala/prism/node/NodeExplorerGrpcServiceImpl.scala index 4880ab21d..73b097f9c 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/NodeExplorerGrpcServiceImpl.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/NodeExplorerGrpcServiceImpl.scala @@ -19,9 +19,7 @@ import io.iohk.atala.prism.protos.node_api import io.iohk.atala.prism.protos.node_api.GetScheduledOperationsRequest.OperationType.{ AnyOperationType, CreateDidOperationOperationType, - IssueCredentialBatchOperationType, ProtocolVersionUpdateOperationType, - RevokeCredentialsOperationType, UpdateDidOperationOperationType } import io.iohk.atala.prism.protos.node_api._ @@ -102,10 +100,6 @@ class NodeExplorerGrpcServiceImpl( o.operation.isDefined && o.operation.get.operation.isCreateDid case UpdateDidOperationOperationType => o.operation.isDefined && o.operation.get.operation.isUpdateDid - case IssueCredentialBatchOperationType => - o.operation.isDefined && o.operation.get.operation.isIssueCredentialBatch - case RevokeCredentialsOperationType => - o.operation.isDefined && o.operation.get.operation.isRevokeCredentials case ProtocolVersionUpdateOperationType => o.operation.isDefined && o.operation.get.operation.isProtocolVersionUpdate case _ => false diff --git a/node/src/main/scala/io/iohk/atala/prism/node/metrics/StatisticsCounters.scala b/node/src/main/scala/io/iohk/atala/prism/node/metrics/StatisticsCounters.scala index e66ab5ca4..4de4e5e1c 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/metrics/StatisticsCounters.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/metrics/StatisticsCounters.scala @@ -12,8 +12,6 @@ object StatisticsCounters { case object NumberOfPendingOperations extends MetricCounter case object NumberOfPublishedDids extends MetricCounter - case object NumberOfIssuedCredentialBatches extends MetricCounter - case object NumberOfCredentialsRevoked extends MetricCounter case object NumberOfAppliedTransactions extends MetricCounter case object NumberOfRejectedTransactions extends MetricCounter } diff --git a/node/src/main/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperation.scala b/node/src/main/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperation.scala deleted file mode 100644 index a1e70e4ed..000000000 --- a/node/src/main/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperation.scala +++ /dev/null @@ -1,130 +0,0 @@ -package io.iohk.atala.prism.node.operations - -import cats.data.EitherT -import cats.syntax.either._ -import doobie.free.connection.ConnectionIO -import doobie.implicits._ -import doobie.postgres.sqlstate -import io.iohk.atala.prism.credentials.CredentialBatchId -import io.iohk.atala.prism.crypto.{MerkleRoot, Sha256, Sha256Digest} -import io.iohk.atala.prism.node.models.DidSuffix -import io.iohk.atala.prism.node.models.nodeState -import io.iohk.atala.prism.node.models.nodeState.{DIDPublicKeyState, LedgerData} -import io.iohk.atala.prism.node.operations.path.{Path, ValueAtPath} -import io.iohk.atala.prism.node.repositories.daos.CredentialBatchesDAO.CreateCredentialBatchData -import io.iohk.atala.prism.node.repositories.daos.{CredentialBatchesDAO, PublicKeysDAO} -import io.iohk.atala.prism.protos.node_models - -import scala.util.Try - -case class IssueCredentialBatchOperation( - credentialBatchId: CredentialBatchId, - issuerDIDSuffix: DidSuffix, - merkleRoot: MerkleRoot, - digest: Sha256Digest, - ledgerData: nodeState.LedgerData -) extends Operation { - override val metricCounterName: String = IssueCredentialBatchOperation.metricCounterName - - override def getCorrectnessData( - keyId: String - ): EitherT[ConnectionIO, StateError, CorrectnessData] = { - for { - keyState <- EitherT[ConnectionIO, StateError, DIDPublicKeyState] { - PublicKeysDAO - .find(issuerDIDSuffix, keyId) - .map(_.toRight(StateError.UnknownKey(issuerDIDSuffix, keyId))) - } - _ <- EitherT.fromEither[ConnectionIO] { - Either.cond( - keyState.revokedOn.isEmpty, - (), - StateError.KeyAlreadyRevoked() - ) - } - data <- EitherT.fromEither[ConnectionIO] { - Either.cond( - keyState.keyUsage.canIssue, - CorrectnessData(keyState.key, None), - StateError.InvalidKeyUsed( - s"The key type expected is Issuing key. Type used: ${keyState.keyUsage}" - ): StateError - ) - } - } yield data - } - - override def applyStateImpl(_config: ApplyOperationConfig): EitherT[ConnectionIO, StateError, Unit] = - for { - _ <- EitherT { - CredentialBatchesDAO - .insert( - CreateCredentialBatchData( - credentialBatchId, - digest, - issuerDIDSuffix, - merkleRoot, - ledgerData - ) - ) - .attemptSomeSqlState { - case sqlstate.class23.UNIQUE_VIOLATION => - StateError.EntityExists( - "credential", - credentialBatchId.getId - ): StateError - case sqlstate.class23.FOREIGN_KEY_VIOLATION => - // that shouldn't happen, as key verification requires issuer in the DB, - // but putting it here just in the case - StateError.EntityMissing("issuerDID", issuerDIDSuffix.getValue) - } - } - } yield () -} - -object IssueCredentialBatchOperation extends SimpleOperationCompanion[IssueCredentialBatchOperation] { - val metricCounterName: String = "number_of_issued_credential_batches" - - override def parse( - operation: node_models.AtalaOperation, - ledgerData: LedgerData - ): Either[ValidationError, IssueCredentialBatchOperation] = { - val operationDigest = Sha256.compute(operation.toByteArray) - val issueCredentialBatchOperation = - ValueAtPath(operation, Path.root) - .child(_.getIssueCredentialBatch, "issueCredentialBatch") - - for { - credentialBatchData <- issueCredentialBatchOperation.childGet( - _.credentialBatchData, - "credentialBatchData" - ) - batchId <- credentialBatchData.parse { _ => - Option( - CredentialBatchId - .fromString( - Sha256.compute(credentialBatchData.value.toByteArray).getHexValue - ) - ).fold("Credential batchId".asLeft[CredentialBatchId])(Right(_)) - } - issuerDIDSuffix <- credentialBatchData - .child(_.issuerDid, "issuerDID") - .parse { issuerDID => - DidSuffix.fromString(issuerDID).toEither.left.map(_.getMessage) - } - merkleRoot <- credentialBatchData - .child(_.merkleRoot, "merkleRoot") - .parse { merkleRoot => - Try( - new MerkleRoot(Sha256Digest.fromBytes(merkleRoot.toByteArray)) - ).toEither.left.map(_.getMessage) - } - } yield IssueCredentialBatchOperation( - batchId, - issuerDIDSuffix, - merkleRoot, - operationDigest, - ledgerData - ) - } -} diff --git a/node/src/main/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperation.scala b/node/src/main/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperation.scala deleted file mode 100644 index 95960634d..000000000 --- a/node/src/main/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperation.scala +++ /dev/null @@ -1,153 +0,0 @@ -package io.iohk.atala.prism.node.operations - -import cats.data.EitherT -import cats.free.Free -import cats.implicits.catsSyntaxEitherId -import cats.syntax.functor._ -import doobie.free.connection.ConnectionIO -import doobie.implicits._ -import io.iohk.atala.prism.credentials.CredentialBatchId -import io.iohk.atala.prism.crypto.{Sha256, Sha256Digest} -import io.iohk.atala.prism.node.models.DidSuffix -import io.iohk.atala.prism.node.models.nodeState -import io.iohk.atala.prism.node.models.nodeState.{DIDPublicKeyState, LedgerData} -import io.iohk.atala.prism.node.operations.path.{Path, ValueAtPath} -import io.iohk.atala.prism.node.repositories.daos.{CredentialBatchesDAO, PublicKeysDAO} -import io.iohk.atala.prism.protos.node_models - -case class RevokeCredentialsOperation( - credentialBatchId: CredentialBatchId, - credentialsToRevoke: List[Sha256Digest], - previousOperation: Sha256Digest, - digest: Sha256Digest, - ledgerData: nodeState.LedgerData -) extends Operation { - override val metricCounterName: String = RevokeCredentialsOperation.metricCounterName - - override def linkedPreviousOperation: Option[Sha256Digest] = Some( - previousOperation - ) - - override def getCorrectnessData( - keyId: String - ): EitherT[ConnectionIO, StateError, CorrectnessData] = { - for { - issuerPrevOp <- EitherT[ - ConnectionIO, - StateError, - (DidSuffix, Sha256Digest) - ] { - CredentialBatchesDAO - .findBatch(credentialBatchId) - .map( - _.map(cred => (cred.issuerDIDSuffix, cred.lastOperation)) - .toRight( - StateError - .EntityMissing("credential batch", credentialBatchId.getId) - ) - ) - } - (issuer, prevOp) = issuerPrevOp - keyState <- EitherT[ConnectionIO, StateError, DIDPublicKeyState] { - PublicKeysDAO - .find(issuer, keyId) - .map(_.toRight(StateError.UnknownKey(issuer, keyId))) - }.subflatMap { didKey => - Either.cond( - didKey.keyUsage.canRevoke, - didKey, - StateError.InvalidKeyUsed( - s"The key type expected is Revocation key. Type used: ${didKey.keyUsage}" - ): StateError - ) - } - _ <- EitherT.fromEither[ConnectionIO] { - Either.cond( - keyState.revokedOn.isEmpty, - (), - StateError.KeyAlreadyRevoked(): StateError - ) - } - } yield CorrectnessData(keyState.key, Some(prevOp)) - } - - override def applyStateImpl(_config: ApplyOperationConfig): EitherT[ConnectionIO, StateError, Unit] = { - def weShouldRevokeTheFullBatch: Boolean = credentialsToRevoke.isEmpty - - def revokeFullBatch() = { - CredentialBatchesDAO - .revokeEntireBatch(credentialBatchId, ledgerData) - .map { wasUpdated => - if (wasUpdated) ().asRight[StateError] - else StateError.BatchAlreadyRevoked(credentialBatchId.getId).asLeft - } - } - - def revokeSpecificCredentials() = { - CredentialBatchesDAO.findBatch(credentialBatchId).flatMap { state => - val isBatchAlreadyRevoked = state.fold(false)(_.revokedOn.nonEmpty) - if (isBatchAlreadyRevoked) { - Free.pure( - (StateError.BatchAlreadyRevoked( - credentialBatchId.getId - ): StateError).asLeft[Unit] - ) - } else { - CredentialBatchesDAO - .revokeCredentials( - credentialBatchId, - credentialsToRevoke, - ledgerData - ) - .as(().asRight[StateError]) - } - } - } - - EitherT[ConnectionIO, StateError, Unit] { - if (weShouldRevokeTheFullBatch) revokeFullBatch() - else revokeSpecificCredentials() - } - } -} - -object RevokeCredentialsOperation extends SimpleOperationCompanion[RevokeCredentialsOperation] { - val metricCounterName: String = "number_of_revoked_credentials" - - override def parse( - operation: node_models.AtalaOperation, - ledgerData: LedgerData - ): Either[ValidationError, RevokeCredentialsOperation] = { - - val operationDigest = Sha256.compute(operation.toByteArray) - val revokeOperation = ValueAtPath(operation, Path.root) - .child(_.getRevokeCredentials, "revokeCredentials") - - for { - credentialBatchId <- revokeOperation - .child(_.credentialBatchId, "credentialBatchId") - .parse { credentialBatchId => - Option( - CredentialBatchId - .fromString(credentialBatchId) - ).fold( - s"credential batch id has invalid format $credentialBatchId" - .asLeft[CredentialBatchId] - )(_.asRight) - } - credentialsToRevoke <- - ParsingUtils.parseHashList( - revokeOperation.child(_.credentialsToRevoke, "credentialsToRevoke") - ) - previousOperation <- ParsingUtils.parseHash( - revokeOperation.child(_.previousOperationHash, "previousOperationHash") - ) - } yield RevokeCredentialsOperation( - credentialBatchId, - credentialsToRevoke, - previousOperation, - operationDigest, - ledgerData - ) - } -} diff --git a/node/src/main/scala/io/iohk/atala/prism/node/operations/package.scala b/node/src/main/scala/io/iohk/atala/prism/node/operations/package.scala index 243f1178f..9d4edccdd 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/operations/package.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/operations/package.scala @@ -320,10 +320,6 @@ package object operations { CreateDIDOperation.parse(signedOperation, ledgerData) case _: node_models.AtalaOperation.Operation.UpdateDid => UpdateDIDOperation.parse(signedOperation, ledgerData) - case _: node_models.AtalaOperation.Operation.IssueCredentialBatch => - IssueCredentialBatchOperation.parse(signedOperation, ledgerData) - case _: node_models.AtalaOperation.Operation.RevokeCredentials => - RevokeCredentialsOperation.parse(signedOperation, ledgerData) case _: node_models.AtalaOperation.Operation.ProtocolVersionUpdate => ProtocolVersionUpdateOperation.parse(signedOperation, ledgerData) case _: node_models.AtalaOperation.Operation.DeactivateDid => @@ -336,6 +332,14 @@ package object operations { "Empty operation" ) ) + case op => // we need to discard unrecognized operations + Left( + InvalidValue( + Path.root, + op.getClass.getSimpleName, + s"Unsupported ${op.getClass.getSimpleName}" + ) + ) } } diff --git a/node/src/main/scala/io/iohk/atala/prism/node/operations/protocolVersion.scala b/node/src/main/scala/io/iohk/atala/prism/node/operations/protocolVersion.scala index 916129aa0..3032048bf 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/operations/protocolVersion.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/operations/protocolVersion.scala @@ -27,8 +27,8 @@ package object protocolVersion { (protocolV, operation) match { case ( ProtocolVersion1_0, - _: CreateDIDOperation | _: UpdateDIDOperation | _: IssueCredentialBatchOperation | - _: RevokeCredentialsOperation | _: ProtocolVersionUpdateOperation | _: DeactivateDIDOperation + _: CreateDIDOperation | _: UpdateDIDOperation | + _: ProtocolVersionUpdateOperation | _: DeactivateDIDOperation ) => true case _ => false diff --git a/node/src/main/scala/io/iohk/atala/prism/node/services/StatisticsService.scala b/node/src/main/scala/io/iohk/atala/prism/node/services/StatisticsService.scala index f500d0467..6c5ee1310 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/services/StatisticsService.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/services/StatisticsService.scala @@ -10,18 +10,12 @@ import io.iohk.atala.prism.node.errors import io.iohk.atala.prism.node.metrics.StatisticsCounters import io.iohk.atala.prism.node.metrics.StatisticsCounters.MetricCounter.{ NumberOfAppliedTransactions, - NumberOfCredentialsRevoked, - NumberOfIssuedCredentialBatches, NumberOfPendingOperations, NumberOfPublishedDids, NumberOfRejectedTransactions } import io.iohk.atala.prism.node.models.AtalaOperationStatus -import io.iohk.atala.prism.node.operations.{ - CreateDIDOperation, - IssueCredentialBatchOperation, - RevokeCredentialsOperation -} +import io.iohk.atala.prism.node.operations.CreateDIDOperation import io.iohk.atala.prism.node.repositories.{AtalaOperationsRepository, MetricsCountersRepository} import io.iohk.atala.prism.node.services.logs.StatisticsServiceLogs import tofu.higherKind.Mid @@ -53,10 +47,6 @@ private final class StatisticsServiceImpl[F[_]: Applicative]( getAtalaOperationsCountByStatus(AtalaOperationStatus.RECEIVED) case NumberOfPublishedDids => metricsCountersRepository.getCounter(CreateDIDOperation.metricCounterName).map(Right(_)) - case NumberOfIssuedCredentialBatches => - metricsCountersRepository.getCounter(IssueCredentialBatchOperation.metricCounterName).map(Right(_)) - case NumberOfCredentialsRevoked => - metricsCountersRepository.getCounter(RevokeCredentialsOperation.metricCounterName).map(Right(_)) case NumberOfAppliedTransactions => getAtalaOperationsCountByStatus(AtalaOperationStatus.APPLIED) case NumberOfRejectedTransactions => diff --git a/node/src/main/scala/io/iohk/atala/prism/node/services/models/package.scala b/node/src/main/scala/io/iohk/atala/prism/node/services/models/package.scala index c43621e5b..df7e446b9 100644 --- a/node/src/main/scala/io/iohk/atala/prism/node/services/models/package.scala +++ b/node/src/main/scala/io/iohk/atala/prism/node/services/models/package.scala @@ -7,9 +7,7 @@ import io.iohk.atala.prism.node.errors.NodeError import io.iohk.atala.prism.node.operations.{ CreateDIDOperation, DeactivateDIDOperation, - IssueCredentialBatchOperation, ProtocolVersionUpdateOperation, - RevokeCredentialsOperation, UpdateDIDOperation, ValidationError, parseOperationWithMockedLedger @@ -50,18 +48,6 @@ package object models { node_models.UpdateDIDOutput() ) ) - case IssueCredentialBatchOperation(credentialBatchId, _, _, _, _) => - OperationOutput( - OperationOutput.Result.BatchOutput( - node_models.IssueCredentialBatchOutput(credentialBatchId.getId) - ) - ) - case RevokeCredentialsOperation(_, _, _, _, _) => - OperationOutput( - OperationOutput.Result.RevokeCredentialsOutput( - node_models.RevokeCredentialsOutput() - ) - ) case ProtocolVersionUpdateOperation(_, _, _, _, _, _) => OperationOutput( OperationOutput.Result.ProtocolVersionUpdateOutput( diff --git a/node/src/test/scala/io/iohk/atala/prism/node/NodeExplorerServiceSpec.scala b/node/src/test/scala/io/iohk/atala/prism/node/NodeExplorerServiceSpec.scala index b2274523a..9700f8289 100644 --- a/node/src/test/scala/io/iohk/atala/prism/node/NodeExplorerServiceSpec.scala +++ b/node/src/test/scala/io/iohk/atala/prism/node/NodeExplorerServiceSpec.scala @@ -33,8 +33,7 @@ import io.iohk.atala.prism.node.services.{ import io.iohk.atala.prism.node.nonce.{ClientHelper, RequestAuthenticator} import io.iohk.atala.prism.protos.node_api.GetScheduledOperationsRequest.OperationType.{ AnyOperationType, - CreateDidOperationOperationType, - IssueCredentialBatchOperationType + CreateDidOperationOperationType } import io.iohk.atala.prism.protos.node_api.NodeExplorerServiceGrpc.NodeExplorerServiceBlockingClient import io.iohk.atala.prism.protos.node_api._ @@ -196,7 +195,6 @@ class NodeExplorerServiceSpec val ops2 = List[node_models.AtalaOperation]( UpdateDIDOperationSpec.exampleRemoveOperation, - IssueCredentialBatchOperationSpec.exampleOperation, CreateDIDOperationSpec.exampleOperationWithCompressedKeys ) @@ -205,14 +203,11 @@ class NodeExplorerServiceSpec CreateDIDOperationSpec.exampleOperation, UpdateDIDOperationSpec.exampleAddAndRemoveOperation, UpdateDIDOperationSpec.exampleRemoveOperation, - IssueCredentialBatchOperationSpec.exampleOperation, CreateDIDOperationSpec.exampleOperationWithCompressedKeys ) val opsCreation: List[node_models.AtalaOperation] = List(CreateDIDOperationSpec.exampleOperation, CreateDIDOperationSpec.exampleOperationWithCompressedKeys) - val opsIssuance: List[node_models.AtalaOperation] = List(IssueCredentialBatchOperationSpec.exampleOperation) - val obj1 = toAtalaObject(ops1) val obj2 = toAtalaObject(ops2) @@ -229,12 +224,9 @@ class NodeExplorerServiceSpec withNonce(service).getScheduledOperations(GetScheduledOperationsRequest(AnyOperationType)) val responseCreation = withNonce(service).getScheduledOperations(GetScheduledOperationsRequest(CreateDidOperationOperationType)) - val responseIssuance = - withNonce(service).getScheduledOperations(GetScheduledOperationsRequest(IssueCredentialBatchOperationType)) responseAny.scheduledOperations.map(_.operation.get) must be(allOps) responseCreation.scheduledOperations.map(_.operation.get) must be(opsCreation) - responseIssuance.scheduledOperations.map(_.operation.get) must be(opsIssuance) } } } diff --git a/node/src/test/scala/io/iohk/atala/prism/node/NodeServiceSpec.scala b/node/src/test/scala/io/iohk/atala/prism/node/NodeServiceSpec.scala index 20d470200..21e48d811 100644 --- a/node/src/test/scala/io/iohk/atala/prism/node/NodeServiceSpec.scala +++ b/node/src/test/scala/io/iohk/atala/prism/node/NodeServiceSpec.scala @@ -315,95 +315,6 @@ class NodeServiceSpec } } - "NodeService.issueCredentialBatch" should { - "schedule IssueCredentialBatch operation" in { - val operation = BlockProcessingServiceSpec.signOperation( - IssueCredentialBatchOperationSpec.exampleOperation, - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - val operationId = AtalaOperationId.of(operation) - mockOperationId(operationId) - - val response = service - .scheduleOperations( - node_api.ScheduleOperationsRequest(List(operation)) - ) - .outputs - .head - - val expectedBatchId = Sha256 - .compute( - IssueCredentialBatchOperationSpec.exampleOperation.getIssueCredentialBatch.getCredentialBatchData.toByteArray - ) - .getHexValue - - response.getBatchOutput.batchId mustBe expectedBatchId - response.getOperationId mustEqual operationId.toProtoByteString - verify(objectManagementService).scheduleAtalaOperations(operation) - verifyNoMoreInteractions(objectManagementService) - } - - "return error when provided operation is invalid" in { - val operation = BlockProcessingServiceSpec.signOperation( - IssueCredentialBatchOperationSpec.exampleOperation - .update( - _.issueCredentialBatch.credentialBatchData.merkleRoot := ByteString - .copyFrom("abc".getBytes) - ), - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - - val error = intercept[StatusRuntimeException] { - service.scheduleOperations( - node_api.ScheduleOperationsRequest(List(operation)) - ) - } - error.getStatus.getCode mustEqual Status.Code.INVALID_ARGUMENT - } - } - - "NodeService.revokeCredentials" should { - "schedule RevokeCredentials operation" in { - val operation = BlockProcessingServiceSpec.signOperation( - RevokeCredentialsOperationSpec.revokeFullBatchOperation, - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - val operationId = AtalaOperationId.of(operation) - mockOperationId(operationId) - - val response = service - .scheduleOperations( - node_api.ScheduleOperationsRequest(List(operation)) - ) - .outputs - .head - - response.getOperationId mustEqual operationId.toProtoByteString - verify(objectManagementService).scheduleAtalaOperations(operation) - verifyNoMoreInteractions(objectManagementService) - } - - "return error when provided operation is invalid" in { - val operation = BlockProcessingServiceSpec.signOperation( - RevokeCredentialsOperationSpec.revokeFullBatchOperation.update( - _.revokeCredentials.credentialBatchId := "" - ), - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - - val error = intercept[StatusRuntimeException] { - service.scheduleOperations( - node_api.ScheduleOperationsRequest(List(operation)) - ) - } - error.getStatus.getCode mustEqual Status.Code.INVALID_ARGUMENT - } - } - "NodeService.getBuildInfo" should { "return proper build and protocol information" in { val currentNetworkProtocolMajorVersion = 2 @@ -825,10 +736,9 @@ class NodeServiceSpec ) val invalidOperation = BlockProcessingServiceSpec.signOperation( - IssueCredentialBatchOperationSpec.exampleOperation + CreateDIDOperationSpec.exampleOperation .update( - _.issueCredentialBatch.credentialBatchData.merkleRoot := ByteString - .copyFrom("abc".getBytes) + _.createDid.didData.context := Seq("abc"), ), "master", CreateDIDOperationSpec.masterKeys.getPrivateKey @@ -845,63 +755,6 @@ class NodeServiceSpec verifyNoMoreInteractions(objectManagementService) } - "properly return the result of a CreateDID operation and an IssueCredentialBatch operation" in { - val createDIDOperation = BlockProcessingServiceSpec.signOperation( - CreateDIDOperationSpec.exampleOperation, - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - val createDIDOperationId = AtalaOperationId.of(createDIDOperation) - - val issuanceOperation = BlockProcessingServiceSpec.signOperation( - IssueCredentialBatchOperationSpec.exampleOperation, - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - val issuanceOperationId = AtalaOperationId.of(issuanceOperation) - - doReturn( - fake[List[Either[NodeError, AtalaOperationId]]]( - List(Right(createDIDOperationId), Right(issuanceOperationId)) - ) - ).when(objectManagementService) - .scheduleAtalaOperations(*) - - val response = service.scheduleOperations( - node_api - .ScheduleOperationsRequest() - .withSignedOperations(Seq(createDIDOperation, issuanceOperation)) - ) - - val expectedBatchId = - Sha256 - .compute( - IssueCredentialBatchOperationSpec.exampleOperation.getIssueCredentialBatch.getCredentialBatchData.toByteArray - ) - .getHexValue - - val expectedDIDSuffix = - Sha256 - .compute(CreateDIDOperationSpec.exampleOperation.toByteArray) - .getHexValue - - response.outputs.size mustBe (2) - - response.outputs.head.getCreateDidOutput.didSuffix mustBe expectedDIDSuffix - response.outputs.head.operationMaybe.operationId.value mustEqual createDIDOperationId.toProtoByteString - response.outputs.head.operationMaybe.error mustBe None - - response.outputs.last.getBatchOutput.batchId mustBe expectedBatchId - response.outputs.last.operationMaybe.operationId.value mustBe issuanceOperationId.toProtoByteString - response.outputs.last.operationMaybe.error mustBe None - - verify(objectManagementService).scheduleAtalaOperations( - createDIDOperation, - issuanceOperation - ) - verifyNoMoreInteractions(objectManagementService) - } - "properly return the result of a CreateDID operation and a DID Update operation" in { val createDIDOperation = BlockProcessingServiceSpec.signOperation( CreateDIDOperationSpec.exampleOperation, @@ -951,37 +804,5 @@ class NodeServiceSpec ) verifyNoMoreInteractions(objectManagementService) } - - "properly return the result of a RevokeCredentials operation" in { - val revokeOperation = BlockProcessingServiceSpec.signOperation( - RevokeCredentialsOperationSpec.revokeFullBatchOperation, - "master", - CreateDIDOperationSpec.masterKeys.getPrivateKey - ) - val revokeOperationId = AtalaOperationId.of(revokeOperation) - - doReturn( - fake[List[Either[NodeError, AtalaOperationId]]]( - List(Right(revokeOperationId)) - ) - ) - .when(objectManagementService) - .scheduleAtalaOperations(*) - - val response = service.scheduleOperations( - node_api - .ScheduleOperationsRequest() - .withSignedOperations(Seq(revokeOperation)) - ) - - response.outputs.size mustBe (1) - response.outputs.head.getRevokeCredentialsOutput mustBe node_models - .RevokeCredentialsOutput() - response.outputs.head.operationMaybe.operationId.value mustEqual revokeOperationId.toProtoByteString - response.outputs.head.operationMaybe.error mustBe None - - verify(objectManagementService).scheduleAtalaOperations(revokeOperation) - verifyNoMoreInteractions(objectManagementService) - } } } diff --git a/node/src/test/scala/io/iohk/atala/prism/node/metrics/OperationsCounterSpec.scala b/node/src/test/scala/io/iohk/atala/prism/node/metrics/OperationsCounterSpec.scala index 25226f52c..c3ff8525d 100644 --- a/node/src/test/scala/io/iohk/atala/prism/node/metrics/OperationsCounterSpec.scala +++ b/node/src/test/scala/io/iohk/atala/prism/node/metrics/OperationsCounterSpec.scala @@ -10,8 +10,6 @@ import org.scalatest.matchers.must.Matchers import io.iohk.atala.prism.node.operations.{ CreateDIDOperationSpec, UpdateDIDOperationSpec, - IssueCredentialBatchOperationSpec, - RevokeCredentialsOperationSpec, ProtocolVersionUpdateOperationSpec, DeactivateDIDOperationSpec } @@ -31,8 +29,6 @@ class OperationsCounterSpec extends AnyWordSpec with Matchers { // Includes all type of update actions val updateDidOperation = sign(UpdateDIDOperationSpec.exampleAllActionsOperation) - val issueCredentialBatchOperation = sign(IssueCredentialBatchOperationSpec.exampleOperation) - val revokeCredentialsOperation = sign(RevokeCredentialsOperationSpec.revokeFullBatchOperation) val protocolVersionUpdateOperation = sign( ProtocolVersionUpdateOperationSpec.protocolUpdateOperation( ProtocolVersionUpdateOperationSpec.protocolVersionInfo1 @@ -43,8 +39,6 @@ class OperationsCounterSpec extends AnyWordSpec with Matchers { val operations = List( createDidOperation, updateDidOperation, - issueCredentialBatchOperation, - revokeCredentialsOperation, protocolVersionUpdateOperation, deactivateDIDOperation ) diff --git a/node/src/test/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperationSpec.scala b/node/src/test/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperationSpec.scala deleted file mode 100644 index 92d5318ac..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/operations/IssueCredentialBatchOperationSpec.scala +++ /dev/null @@ -1,327 +0,0 @@ -package io.iohk.atala.prism.node.operations - -import cats.effect.unsafe.implicits.global -import com.google.protobuf.ByteString -import doobie.implicits._ -import io.iohk.atala.prism.crypto.{MerkleRoot, Sha256} -import io.iohk.atala.prism.node.{AtalaWithPostgresSpec, DataPreparation} -import io.iohk.atala.prism.node.DataPreparation.{dummyApplyOperationConfig, dummyLedgerData} -import io.iohk.atala.prism.node.models.{DIDData, DIDPublicKey, KeyUsage} -import io.iohk.atala.prism.node.repositories.daos.CredentialBatchesDAO -import io.iohk.atala.prism.protos.node_models -import org.scalatest.EitherValues._ -import org.scalatest.Inside.inside -import org.scalatest.OptionValues.convertOptionToValuable - -object IssueCredentialBatchOperationSpec { - val masterKeys = CreateDIDOperationSpec.masterKeys - val issuingKeys = CreateDIDOperationSpec.issuingKeys - - lazy val issuerDidKeys = List( - DIDPublicKey( - issuerDIDSuffix, - "master", - KeyUsage.MasterKey, - masterKeys.getPublicKey - ), - DIDPublicKey( - issuerDIDSuffix, - "issuing", - KeyUsage.IssuingKey, - issuingKeys.getPublicKey - ) - ) - - lazy val issuerCreateDIDOperation = - CreateDIDOperation - .parse(CreateDIDOperationSpec.exampleOperation, dummyLedgerData) - .toOption - .value - lazy val issuerDIDSuffix = issuerCreateDIDOperation.id - val content = "" - val mockMerkleRoot = new MerkleRoot(Sha256.compute(content.getBytes)) - - val exampleOperation = node_models.AtalaOperation( - operation = node_models.AtalaOperation.Operation.IssueCredentialBatch( - value = node_models.IssueCredentialBatchOperation( - credentialBatchData = Some( - node_models.CredentialBatchData( - issuerDid = issuerDIDSuffix.getValue, - merkleRoot = ByteString.copyFrom(mockMerkleRoot.getHash.getValue) - ) - ) - ) - ) - ) -} - -class IssueCredentialBatchOperationSpec extends AtalaWithPostgresSpec { - - import IssueCredentialBatchOperationSpec._ - - "IssueCredentialBatchOperation.parse" should { - "parse valid IssueCredentialBatchOperation AtalaOperation" in { - IssueCredentialBatchOperation.parse( - exampleOperation, - dummyLedgerData - ) mustBe a[Right[_, _]] - } - - "return error when issuerDID is not provided / empty" in { - val invalidOperation = exampleOperation - .update(_.issueCredentialBatch.credentialBatchData.issuerDid := "") - - inside( - IssueCredentialBatchOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector( - "issueCredentialBatch", - "credentialBatchData", - "issuerDID" - ) - value mustBe "" - } - } - - "return error when issuerDID doesn't have valid form" in { - val invalidOperation = exampleOperation - .update( - _.issueCredentialBatch.credentialBatchData.issuerDid := "my best friend" - ) - - inside( - IssueCredentialBatchOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector( - "issueCredentialBatch", - "credentialBatchData", - "issuerDID" - ) - value mustBe "my best friend" - } - } - - "return error when merkle root is not provided / empty" in { - val invalidOperation = exampleOperation - .update( - _.issueCredentialBatch.credentialBatchData.merkleRoot := ByteString.EMPTY - ) - - inside( - IssueCredentialBatchOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector( - "issueCredentialBatch", - "credentialBatchData", - "merkleRoot" - ) - value mustBe "0x0" - } - } - - "return error when hash has invalid length" in { - val invalidHash = ByteString.copyFrom("abc", "UTF8") - val invalidOperation = exampleOperation - .update( - _.issueCredentialBatch.credentialBatchData.merkleRoot := invalidHash - ) - - inside( - IssueCredentialBatchOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector( - "issueCredentialBatch", - "credentialBatchData", - "merkleRoot" - ) - value mustBe "0x616263" - } - } - } - - "IssueCredentialBatchOperation.getCorrectnessData" should { - "provide the key reference be used for signing" in { - DataPreparation - .createDID( - DIDData( - issuerDIDSuffix, - issuerDidKeys, - Nil, - Nil, - issuerCreateDIDOperation.digest - ), - dummyLedgerData - ) - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - val CorrectnessData(key, previousOperation) = parsedOperation - .getCorrectnessData("issuing") - .transact(database) - .value - .unsafeRunSync() - .toOption - .value - - key mustBe issuingKeys.getPublicKey - previousOperation mustBe None - } - "return state error when there are used different key than issuing key" in { - DataPreparation - .createDID( - DIDData( - issuerDIDSuffix, - issuerDidKeys, - Nil, - Nil, - issuerCreateDIDOperation.digest - ), - dummyLedgerData - ) - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - val result = parsedOperation - .getCorrectnessData("master") - .transact(database) - .value - .unsafeRunSync() - - result mustBe Left( - StateError.InvalidKeyUsed( - "The key type expected is Issuing key. Type used: MasterKey" - ) - ) - } - "return state error when unknown keyId is used" in { - DataPreparation - .createDID( - DIDData( - issuerDIDSuffix, - issuerDidKeys, - Nil, - Nil, - issuerCreateDIDOperation.digest - ), - dummyLedgerData - ) - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - val result = parsedOperation - .getCorrectnessData("issuing3") - .transact(database) - .value - .unsafeRunSync() - - result mustBe Left(StateError.UnknownKey(issuerDIDSuffix, "issuing3")) - } - } - - "IssueCredentialBatchOperation.applyState" should { - "create the credential batch information in the database" in { - DataPreparation - .createDID( - DIDData( - issuerDIDSuffix, - issuerDidKeys, - Nil, - Nil, - issuerCreateDIDOperation.digest - ), - dummyLedgerData - ) - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - val result = parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeToFuture() - .futureValue - result mustBe a[Right[_, _]] - - val insertedBatch = - CredentialBatchesDAO - .findBatch(parsedOperation.credentialBatchId) - .transact(database) - .unsafeToFuture() - .futureValue - .value - - insertedBatch.batchId mustBe parsedOperation.credentialBatchId - insertedBatch.issuerDIDSuffix mustBe parsedOperation.issuerDIDSuffix - insertedBatch.merkleRoot mustBe parsedOperation.merkleRoot - insertedBatch.issuedOn mustBe dummyLedgerData - insertedBatch.lastOperation mustBe parsedOperation.digest - insertedBatch.revokedOn mustBe empty - } - - "return error when issuer is missing in the DB" in { - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - val result = parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeToFuture() - .futureValue - .left - .value - - result mustBe a[StateError.EntityMissing] - } - - "return error when the credential already exists in the db" in { - DataPreparation - .createDID( - DIDData( - issuerDIDSuffix, - issuerDidKeys, - Nil, - Nil, - issuerCreateDIDOperation.digest - ), - dummyLedgerData - ) - - val parsedOperation = IssueCredentialBatchOperation - .parse(exampleOperation, dummyLedgerData) - .toOption - .value - - // first insertion - val resultAttempt1 = parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeToFuture() - .futureValue - - resultAttempt1 mustBe a[Right[_, _]] - - val resultAttempt2 = parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeToFuture() - .futureValue - .left - .value - - resultAttempt2 mustBe a[StateError.EntityExists] - } - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperationSpec.scala b/node/src/test/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperationSpec.scala deleted file mode 100644 index 089505df3..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/operations/RevokeCredentialsOperationSpec.scala +++ /dev/null @@ -1,498 +0,0 @@ -package io.iohk.atala.prism.node.operations - -import cats.effect.unsafe.implicits.global -import com.google.protobuf.ByteString -import doobie.implicits._ -import io.iohk.atala.prism.credentials.CredentialBatchId -import io.iohk.atala.prism.crypto.{Sha256, Sha256Digest} -import io.iohk.atala.prism.protos.models.TimestampInfo -import io.iohk.atala.prism.node.models.{Ledger, TransactionId} -import io.iohk.atala.prism.node.AtalaWithPostgresSpec -import io.iohk.atala.prism.node.DataPreparation.{dummyApplyOperationConfig, dummyLedgerData} -import io.iohk.atala.prism.node.models.nodeState.LedgerData -import io.iohk.atala.prism.node.repositories.daos.CredentialBatchesDAO -import io.iohk.atala.prism.protos.node_models -import org.scalatest.EitherValues._ -import org.scalatest.Inside._ -import org.scalatest.OptionValues._ - -import java.time.Instant - -object RevokeCredentialsOperationSpec { - private val revokingKeys = CreateDIDOperationSpec.revokingKeys - - lazy val issuerCreateDIDOperation: CreateDIDOperation = - CreateDIDOperation - .parse(CreateDIDOperationSpec.exampleOperation, dummyLedgerData) - .toOption - .value - - lazy val credentialIssueBatchOperation: IssueCredentialBatchOperation = - IssueCredentialBatchOperation - .parse( - IssueCredentialBatchOperationSpec.exampleOperation, - dummyLedgerData - ) - .toOption - .value - - lazy val credentialBatchId: CredentialBatchId = - credentialIssueBatchOperation.credentialBatchId - - val revocationDate: TimestampInfo = - new TimestampInfo(Instant.ofEpochMilli(0).toEpochMilli, 0, 1) - val revocationLedgerData: LedgerData = - LedgerData( - TransactionId - .from(Array.fill[Byte](TransactionId.config.size.toBytes.toInt)(0)) - .value, - Ledger.InMemory, - revocationDate - ) - - val revokeFullBatchOperation: node_models.AtalaOperation = - node_models.AtalaOperation( - operation = node_models.AtalaOperation.Operation.RevokeCredentials( - value = node_models.RevokeCredentialsOperation( - previousOperationHash = ByteString.copyFrom(credentialIssueBatchOperation.digest.getValue), - credentialBatchId = credentialBatchId.getId, - credentialsToRevoke = Seq() - ) - ) - ) - - val credentialHashToRevoke: Sha256Digest = Sha256.compute("cred 1".getBytes) - - val revokeSpecificCredentialsOperation: node_models.AtalaOperation = - node_models.AtalaOperation( - operation = node_models.AtalaOperation.Operation.RevokeCredentials( - value = node_models.RevokeCredentialsOperation( - previousOperationHash = ByteString.copyFrom(credentialIssueBatchOperation.digest.getValue), - credentialBatchId = credentialBatchId.getId, - credentialsToRevoke = Seq(ByteString.copyFrom(credentialHashToRevoke.getValue)) - ) - ) - ) -} - -class RevokeCredentialsOperationSpec extends AtalaWithPostgresSpec { - - import RevokeCredentialsOperationSpec._ - - "RevokeCredentialsOperation.parse" should { - "parse valid RevokeCredentials AtalaOperation to revoke a full batch" in { - RevokeCredentialsOperation.parse( - revokeFullBatchOperation, - dummyLedgerData - ) mustBe a[Right[_, _]] - } - - "parse valid RevokeCredentials AtalaOperation to revoke specific credentials within a batch" in { - RevokeCredentialsOperation.parse( - revokeSpecificCredentialsOperation, - dummyLedgerData - ) mustBe a[Right[_, _]] - } - - "return error when no previous operation is provided" in { - val invalidOperation = revokeFullBatchOperation - .update(_.revokeCredentials.previousOperationHash := ByteString.EMPTY) - - inside( - RevokeCredentialsOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector("revokeCredentials", "previousOperationHash") - value mustBe "0x0" - } - } - - "return error when previous operation hash has invalid length" in { - val bs = ByteString.copyFromUtf8("abc") - val invalidOperation = revokeFullBatchOperation - .update(_.revokeCredentials.previousOperationHash := bs) - - inside( - RevokeCredentialsOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector("revokeCredentials", "previousOperationHash") - value mustBe "0x616263" - } - } - - "return error if no credential batch id is provided" in { - val invalidOperation = revokeFullBatchOperation - .update(_.revokeCredentials.credentialBatchId := "") - - inside( - RevokeCredentialsOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector("revokeCredentials", "credentialBatchId") - value mustBe "" - } - } - - "return error if credential batch id has invalid format" in { - val cid = "my last credential" - val invalidOperation = revokeFullBatchOperation - .update(_.revokeCredentials.credentialBatchId := cid) - - inside( - RevokeCredentialsOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector("revokeCredentials", "credentialBatchId") - value mustBe cid - } - } - - "return error if a credential hash to revoke has invalid format" in { - val invalidSeq = Seq(ByteString.copyFrom("my last credential".getBytes())) - val invalidOperation = revokeFullBatchOperation - .update(_.revokeCredentials.credentialsToRevoke := invalidSeq) - - inside( - RevokeCredentialsOperation.parse(invalidOperation, dummyLedgerData) - ) { case Left(ValidationError.InvalidValue(path, value, _)) => - path.path mustBe Vector("revokeCredentials", "credentialsToRevoke") - value mustBe invalidSeq.toString - } - } - } - - "RevokeCredentialsOperation.getCorrectnessData" should { - "provide the data required for correctness verification" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedOperation = RevokeCredentialsOperation - .parse(revokeFullBatchOperation, dummyLedgerData) - .toOption - .value - - val corrDataE = parsedOperation - .getCorrectnessData("revoking") - .transact(database) - .value - .unsafeRunSync() - - val CorrectnessData(key, previousOperation) = corrDataE.toOption.value - - key mustBe revokingKeys.getPublicKey - previousOperation mustBe Some(credentialIssueBatchOperation.digest) - } - "return state error when there are used different key than revocation key" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedOperation = RevokeCredentialsOperation - .parse(revokeFullBatchOperation, dummyLedgerData) - .toOption - .value - - val result = parsedOperation - .getCorrectnessData("master") - .transact(database) - .value - .unsafeRunSync() - - result mustBe Left( - StateError.InvalidKeyUsed( - "The key type expected is Revocation key. Type used: MasterKey" - ) - ) - } - } - - "RevokeCredentialsOperation.applyState" should { - "mark credential batch as revoked in the database" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedOperation = - RevokeCredentialsOperation - .parse(revokeFullBatchOperation, revocationLedgerData) - .toOption - .value - - parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - .toOption - .value - - val credentialBatch = - CredentialBatchesDAO - .findBatch(parsedOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatch.revokedOn mustBe Some(revocationLedgerData) - } - - "fail when attempting to revoke an already revoked credential batch" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedOperation = - RevokeCredentialsOperation - .parse(revokeFullBatchOperation, revocationLedgerData) - .toOption - .value - - parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - .toOption - .value - - val credentialBatch = - CredentialBatchesDAO - .findBatch(parsedOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatch.revokedOn mustBe Some(revocationLedgerData) - - val error = - parsedOperation.applyState(dummyApplyOperationConfig).transact(database).value.unsafeRunSync() - - error.left.value mustBe a[StateError.BatchAlreadyRevoked] - } - - "mark specific credentials as revoked in the database" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedOperation = - RevokeCredentialsOperation - .parse(revokeSpecificCredentialsOperation, revocationLedgerData) - .toOption - .value - - parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - .toOption - .value - - val credentialsRevoked = - CredentialBatchesDAO - .findRevokedCredentials(parsedOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - - credentialsRevoked.size mustBe 1 - val (revokedCredHash, revokedAt) = credentialsRevoked.headOption.value - revokedCredHash mustBe credentialHashToRevoke - revokedAt mustBe revocationLedgerData - - // the batch itself should not be revoked - val credentialBatch = - CredentialBatchesDAO - .findBatch(parsedOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatch.revokedOn mustBe empty - } - - "fail to revoke specific credentials when the batch was already revoked" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedRevokeBatchOperation = - RevokeCredentialsOperation - .parse(revokeFullBatchOperation, revocationLedgerData) - .toOption - .value - - parsedRevokeBatchOperation - .applyState(dummyApplyOperationConfig) - .value - .transact(database) - .unsafeRunSync() - .toOption - .value - - val credentialBatch = - CredentialBatchesDAO - .findBatch(parsedRevokeBatchOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatch.revokedOn mustBe Some(revocationLedgerData) - - val parsedOperation = - RevokeCredentialsOperation - .parse(revokeSpecificCredentialsOperation, dummyLedgerData) - .toOption - .value - - // sanity check - parsedOperation.credentialBatchId mustBe parsedRevokeBatchOperation.credentialBatchId - - val error = parsedOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - .left - .value - - error mustBe a[StateError.BatchAlreadyRevoked] - - val credentialsRevoked = - CredentialBatchesDAO - .findRevokedCredentials(parsedOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - - credentialsRevoked mustBe empty - - // the batch itself should remain revoked with the same time - val credentialBatchAfter = - CredentialBatchesDAO - .findBatch(parsedRevokeBatchOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatchAfter.revokedOn mustBe Some(revocationLedgerData) - } - - "do not update revocation time for specific credentials that were already revoked" in { - issuerCreateDIDOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - credentialIssueBatchOperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - - val parsedFirstOperation = - RevokeCredentialsOperation - .parse(revokeSpecificCredentialsOperation, revocationLedgerData) - .toOption - .value - - parsedFirstOperation - .applyState(dummyApplyOperationConfig) - .value - .transact(database) - .unsafeRunSync() - .toOption - .value - - val credentialsRevoked = - CredentialBatchesDAO - .findRevokedCredentials(parsedFirstOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - - credentialsRevoked.size mustBe 1 - val (revokedCredHash, revokedAt) = credentialsRevoked.headOption.value - revokedCredHash mustBe credentialHashToRevoke - revokedAt mustBe revocationLedgerData - - val parsedOSecondperation = - RevokeCredentialsOperation - .parse(revokeSpecificCredentialsOperation, dummyLedgerData) - .toOption - .value - - // sanity check - parsedOSecondperation.credentialBatchId mustBe parsedFirstOperation.credentialBatchId - - parsedOSecondperation - .applyState(dummyApplyOperationConfig) - .transact(database) - .value - .unsafeRunSync() - .toOption - .value - - val credentialsRevokedAfter = - CredentialBatchesDAO - .findRevokedCredentials(parsedOSecondperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - - credentialsRevokedAfter.size mustBe 1 - val (revokedCredHashAfter, revokedAtAfter) = - credentialsRevokedAfter.headOption.value - revokedCredHashAfter mustBe credentialHashToRevoke - // the time didn't change - revokedAtAfter mustBe revocationLedgerData - - // the batch itself should not be revoked - val credentialBatchAfter = - CredentialBatchesDAO - .findBatch(parsedFirstOperation.credentialBatchId) - .transact(database) - .unsafeRunSync() - .value - - credentialBatchAfter.revokedOn mustBe empty - } - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/CredVerification.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/CredVerification.scala deleted file mode 100644 index 32ff6fd28..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/CredVerification.scala +++ /dev/null @@ -1,139 +0,0 @@ -package io.iohk.atala.prism.node.poc - -import cats.data.{Validated, ValidatedNel} -import cats.implicits.{catsSyntaxTuple6Semigroupal, catsSyntaxValidatedId} -import io.iohk.atala.prism.credentials.PrismCredential -import io.iohk.atala.prism.crypto.{MerkleInclusionProof, MerkleRoot} -import io.iohk.atala.prism.api.CredentialBatches -import io.iohk.atala.prism.node.models.KeyData -import io.iohk.atala.prism.protos.models.TimestampInfo - -object CredVerification { - - sealed trait VerificationError - object VerificationError { - case class CredentialWasRevoked(revokedOn: TimestampInfo) extends VerificationError - case class BatchWasRevoked(revokedOn: TimestampInfo) extends VerificationError - case object InvalidMerkleProof extends VerificationError - case class KeyWasNotValid( - keyAddedOn: TimestampInfo, - credentialIssuedOn: TimestampInfo - ) extends VerificationError - case class KeyWasRevoked( - credentialIssuedOn: TimestampInfo, - keyRevokedOn: TimestampInfo - ) extends VerificationError - case object InvalidSignature extends VerificationError - } - - case class BatchData( - batchIssuanceDate: TimestampInfo, - revocationDate: Option[TimestampInfo] - ) - - import VerificationError._ - - private val valid = ().validNel[VerificationError] - - /** This method receives data retrieved from the node and the credential to verify and returns true if and only if the - * credential is valid. - * - * We have some assumptions to call this method: - * 1. The keyData is obtained from the PRISM node and corresponds to the key used to sign the credential 2. The - * batchData is obtained from the PRISM node and corresponds to the signedCredential parameter 3. The issuer DID - * is a trusted one 4. The credentialRevocationTime is obtained from the PRISM node and corresponds to the - * signedCredential parameter - * - * @param keyData - * the public key used to sign the credential and its addition and (optional) revocation timestamps - * @param batchData - * the credential information extracted from the node - * @param credentialRevocationTime - * the credential information extracted from the node - * @param merkleRoot - * merkle root that represents the batch - * @param inclusionProof - * merkle proof of inclusion that states that signedCredential is in the batch - * @param signedCredential - * the credential to verify - * @return - * a validation result - */ - def verify( - keyData: KeyData, - batchData: BatchData, - credentialRevocationTime: Option[TimestampInfo], - merkleRoot: MerkleRoot, - inclusionProof: MerkleInclusionProof, - signedCredential: PrismCredential - ): ValidatedNel[VerificationError, Unit] = { - - // Scala's type system is evil, so we need this type alias to currify things for the - // compiler (see https://stackoverflow.com/questions/49865936/scala-cats-validated-value-mapn-is-not-a-member-of-validatednel-tuple) - type ValidationResult[A] = ValidatedNel[VerificationError, A] - - // the credential batch is not revoked - val credentialBatchNotRevoked: ValidationResult[Unit] = - batchData.revocationDate.fold(valid) { revokedOn => - BatchWasRevoked(revokedOn = revokedOn).invalidNel - } - - // the key was added before the credential was issued - val keyAddedBeforeIssuance: ValidationResult[Unit] = - Validated.condNel( - keyData.addedOn occurredBefore batchData.batchIssuanceDate, - (), - KeyWasNotValid( - keyAddedOn = keyData.addedOn, - credentialIssuedOn = batchData.batchIssuanceDate - ) - ) - - // the key is not revoked or, the key was revoked after the credential was signed - val keyWasStillValid: ValidationResult[Unit] = { - keyData.revokedOn match { - case None => // the key was not revoked - valid - case Some(revokedOn) => - if (batchData.batchIssuanceDate occurredBefore revokedOn) valid - else - KeyWasRevoked( - credentialIssuedOn = batchData.batchIssuanceDate, - keyRevokedOn = revokedOn - ).invalidNel - } - } - - // the signature is valid - val signatureIsValid: ValidationResult[Unit] = - Validated.condNel( - signedCredential.isValidSignature(keyData.issuingKey), - (), - InvalidSignature - ) - - val individualCredentialNotRevoked: ValidationResult[Unit] = - credentialRevocationTime.fold(valid) { revokedOn => - CredentialWasRevoked(revokedOn).invalidNel - } - - val merkleProofIsValid: ValidationResult[Unit] = - Validated.condNel( - CredentialBatches - .verifyInclusion(signedCredential, merkleRoot, inclusionProof), - (), - InvalidMerkleProof - ) - - ( - credentialBatchNotRevoked, - keyAddedBeforeIssuance, - keyWasStillValid, - signatureIsValid, - individualCredentialNotRevoked, - merkleProofIsValid - ).mapN { (_: Unit, _: Unit, _: Unit, _: Unit, _: Unit, _: Unit) => - () - } - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/EncodedSizes.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/EncodedSizes.scala deleted file mode 100644 index 928c707bb..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/EncodedSizes.scala +++ /dev/null @@ -1,79 +0,0 @@ -package io.iohk.atala.prism.node.poc - -import java.util.Base64 -import com.google.protobuf.ByteString -import io.iohk.atala.prism.crypto.EC.{INSTANCE => EC} -import io.iohk.atala.prism.crypto.keys.ECPublicKey -import io.iohk.atala.prism.crypto.ECConfig.{INSTANCE => ECConfig} -import io.iohk.atala.prism.crypto.Sha256 -import io.iohk.atala.prism.node.models.DidSuffix -import io.iohk.atala.prism.protos.node_models - -object EncodedSizes { - def main(args: Array[String]): Unit = { - val startTime = System.currentTimeMillis() - - val n = 100000 - println(s"Generating $n dids") - - val data = for { - _ <- 1 to n - masterPublicKey1 = EC.generateKeyPair().getPublicKey - masterPublicKey2 = EC.generateKeyPair().getPublicKey - masterPublicKey3 = EC.generateKeyPair().getPublicKey - did = createDID( - List(masterPublicKey1, masterPublicKey2, masterPublicKey3) - ) - } yield (did, did.length) - - val sortedData = data.sortBy(_._2) - println("printing 3 shortest DIDs") - println(sortedData.take(3).mkString("\n")) - println("printing 3 longest DIDs") - println(sortedData.drop(n - 3).mkString("\n")) - - val averageSize = data.foldLeft(0) { _ + _._2 } / n.toDouble - println(s"Average DID length $averageSize bytes") - val endTime = System.currentTimeMillis() - - println(s"Dataset generated in ${(endTime - startTime) / 1000.0} seconds") - - } - - def createDID(masterPublicKeys: List[ECPublicKey]): String = { - def keyElement(publicKey: ECPublicKey, index: Int): node_models.PublicKey = - node_models.PublicKey( - id = s"master$index", - usage = node_models.KeyUsage.MASTER_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(publicKey) - ) - ) - - val createDidOp = node_models.CreateDIDOperation( - didData = Some( - node_models.CreateDIDOperation.DIDCreationData( - publicKeys = masterPublicKeys.zipWithIndex map { case (k, i) => - keyElement(k, i) - } - ) - ) - ) - - val atalaOp = node_models.AtalaOperation(operation = node_models.AtalaOperation.Operation.CreateDid(createDidOp)) - val operationBytes = atalaOp.toByteArray - val operationHash = Sha256.compute(operationBytes) - val didSuffix: DidSuffix = DidSuffix.fromDigest(operationHash) - val encodedOperation = Base64.getUrlEncoder.encodeToString(operationBytes) - s"did:prism:${didSuffix.getValue}:$encodedOperation" - } - - private def publicKeyToProto(key: ECPublicKey): node_models.ECKeyData = { - val point = key.getCurvePoint - node_models.ECKeyData( - curve = ECConfig.getCURVE_NAME, - x = ByteString.copyFrom(point.getX.bytes()), - y = ByteString.copyFrom(point.getY.bytes()) - ) - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/GenericCredentialsSDK.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/GenericCredentialsSDK.scala deleted file mode 100644 index c564b53c0..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/GenericCredentialsSDK.scala +++ /dev/null @@ -1,45 +0,0 @@ -package io.iohk.atala.prism.node.poc - -import io.iohk.atala.prism.credentials.content.CredentialContent -import io.iohk.atala.prism.identity.{PrismDid => DID} -import io.iohk.atala.prism.node.models.DidSuffix -import kotlinx.serialization.json.JsonElementKt.JsonPrimitive -import kotlinx.serialization.json.JsonObject - -import scala.annotation.nowarn -import scala.jdk.CollectionConverters._ -// This SDK would allow to build generic credentials and manipulate them -// For this toy example, the credential model is a String that represents a JSON -// and we didn't add nice builders, we just take fixed values for illustration -// to build a degree credential -object GenericCredentialsSDK { - - private var issuerDIDUsed: DID = _ - private var keyIdUsed: String = "" - - def buildGenericCredential( - credentialType: String, - issuerDID: DID, - issuanceKeyId: String, - claims: String - ): CredentialContent = { - issuerDIDUsed = issuerDID - keyIdUsed = issuanceKeyId - val fields = Map( - "type" -> JsonPrimitive(credentialType), - "id" -> JsonPrimitive(s"did:prism:${issuerDID.getSuffix}"), - "keyId" -> JsonPrimitive(issuanceKeyId), - "credentialSubject" -> JsonPrimitive(claims) - ) - new CredentialContent(new JsonObject(fields.asJava)) - } - - @nowarn("cat=unused-params") - def getIssuerDID(credential: String): String = issuerDIDUsed.getValue - @nowarn("cat=unused-params") - def getIssuerDIDSufix(credential: String): DidSuffix = DidSuffix( - issuerDIDUsed.getSuffix - ) - @nowarn("cat=unused-params") - def getKeyId(credential: String): String = keyIdUsed -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/Wallet.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/Wallet.scala deleted file mode 100644 index 72dcdfa94..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/Wallet.scala +++ /dev/null @@ -1,237 +0,0 @@ -package io.iohk.atala.prism.node.poc - -import cats.data.ValidatedNel -import com.google.protobuf.ByteString -import io.iohk.atala.prism.credentials._ -import io.iohk.atala.prism.credentials.json.JsonBasedCredential -import io.iohk.atala.prism.credentials.content.CredentialContent -import io.iohk.atala.prism.crypto.{MerkleInclusionProof, Sha256} -import io.iohk.atala.prism.crypto.EC.{INSTANCE => EC} -import io.iohk.atala.prism.crypto.keys.{ECPrivateKey, ECPublicKey} -import io.iohk.atala.prism.crypto.ECConfig.{INSTANCE => ECConfig} -import io.iohk.atala.prism.protos.{node_api, node_models} -import io.iohk.atala.prism.crypto.signature.ECSignature -import io.iohk.atala.prism.identity.PrismDid -import io.iohk.atala.prism.node.models.{DidSuffix, KeyData} -import io.iohk.atala.prism.node.grpc.ProtoCodecs -import io.iohk.atala.prism.node.poc.CredVerification.{BatchData, VerificationError} -import org.scalatest.OptionValues.convertOptionToValuable - -// We define some classes to illustrate what happens in the different components -case class Wallet(node: node_api.NodeServiceGrpc.NodeServiceBlockingStub) { - - private var dids: Map[DidSuffix, collection.mutable.Map[String, ECPrivateKey]] = Map() - - def generateDID(): (DidSuffix, node_models.AtalaOperation) = { - val masterKeyPair = EC.generateKeyPair() - val masterPrivateKey = masterKeyPair.getPrivateKey - val masterPublicKey = masterKeyPair.getPublicKey - val issuanceKeyPair = EC.generateKeyPair() - val issuancePrivateKey = issuanceKeyPair.getPrivateKey - val issuancePublicKey = issuanceKeyPair.getPublicKey - - // This could be encapsulated in the "NodeSDK". I added it here for simplicity - // Note that in our current design we cannot create a did that has two keys from start - val createDidOp = node_models.CreateDIDOperation( - didData = Some( - node_models.CreateDIDOperation.DIDCreationData( - publicKeys = Seq( - node_models.PublicKey( - id = PrismDid.getDEFAULT_MASTER_KEY_ID, - usage = node_models.KeyUsage.MASTER_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(masterPublicKey) - ) - ), - node_models.PublicKey( - id = "issuance0", - usage = node_models.KeyUsage.ISSUING_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(issuancePublicKey) - ) - ) - ) - ) - ) - ) - - val atalaOp = node_models.AtalaOperation(operation = node_models.AtalaOperation.Operation.CreateDid(createDidOp)) - val operationHash = Sha256.compute(atalaOp.toByteArray) - val didSuffix: DidSuffix = DidSuffix(operationHash.getHexValue) - - dids += (didSuffix -> collection.mutable.Map( - PrismDid.getDEFAULT_MASTER_KEY_ID -> masterPrivateKey, - "issuance0" -> issuancePrivateKey - )) - - (didSuffix, atalaOp) - } - - def addRevocationKeyToDid( - revocationKeyId: String, - previousOperationHash: ByteString, - didSuffix: DidSuffix - ): Unit = { - val revocationKeyPair = EC.generateKeyPair() - val publicKeyProto = node_models.PublicKey( - id = revocationKeyId, - usage = node_models.KeyUsage.REVOCATION_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(revocationKeyPair.getPublicKey) - ) - ) - - val updateDIDOp = node_models.UpdateDIDOperation( - previousOperationHash = previousOperationHash, - id = didSuffix.getValue, - actions = Seq( - node_models.UpdateDIDAction( - node_models.UpdateDIDAction.Action.AddKey( - node_models.AddKeyAction( - Some(publicKeyProto) - ) - ) - ) - ) - ) - val updateDidOpSigned = signOperation( - node_models.AtalaOperation( - node_models.AtalaOperation.Operation.UpdateDid(updateDIDOp) - ), - PrismDid.getDEFAULT_MASTER_KEY_ID, - didSuffix - ) - node.scheduleOperations(node_api.ScheduleOperationsRequest(List(updateDidOpSigned))) - dids(didSuffix) += (revocationKeyId -> revocationKeyPair.getPrivateKey) - () - } - - def signOperation( - operation: node_models.AtalaOperation, - keyId: String, - didSuffix: DidSuffix - ): node_models.SignedAtalaOperation = { - // TODO: This logic should also live eventually in the crypto library - val key = dids(didSuffix)(keyId) - node_models.SignedAtalaOperation( - signedWith = keyId, - operation = Some(operation), - signature = ByteString.copyFrom(EC.signBytes(operation.toByteArray, key).getData) - ) - } - - def signCredential( - credentialContent: CredentialContent, - keyId: String, - didSuffix: DidSuffix - ): PrismCredential = { - val privateKey = dids(didSuffix)(keyId) - new JsonBasedCredential(credentialContent, null).sign(privateKey) - } - - def signKey( - publicKey: ECPublicKey, - keyId: String, - didSuffix: DidSuffix - ): ECSignature = { - val privateKey = dids(didSuffix)(keyId) - EC.signBytes(publicKey.getEncoded, privateKey) - } - - def verifySignedKey( - publicKey: ECPublicKey, - signature: ECSignature, - signingKey: ECPublicKey - ): Boolean = { - EC.verifyBytes(publicKey.getEncoded, signingKey, signature) - } - - def verifyCredential( - credential: PrismCredential, - merkleProof: MerkleInclusionProof - ): ValidatedNel[VerificationError, Unit] = { - // extract user DIDSuffix and keyId from credential - val issuerDID = Option(credential.getContent.getIssuerDid) - .getOrElse(throw new Exception("getIssuerDid is null")) - val issuanceKeyId = - Option(credential.getContent.getIssuanceKeyId) - .getOrElse(throw new Exception("getIssuanceKeyId is null")) - - // request credential state to the node - val merkleRoot = merkleProof.derivedRoot - val batchId = - CredentialBatchId.fromBatchData(issuerDID.getSuffix, merkleRoot) - - val batchStateProto = node.getBatchState( - node_api.GetBatchStateRequest( - batchId.getId - ) - ) - val batchIssuanceDate = - ProtoCodecs.fromTimestampInfoProto( - batchStateProto.getPublicationLedgerData.timestampInfo.value - ) - val batchRevocationDate = - batchStateProto.getRevocationLedgerData.timestampInfo.map( - ProtoCodecs.fromTimestampInfoProto - ) - val batchData = BatchData(batchIssuanceDate, batchRevocationDate) - - // resolve DID through the node - val didDocumentOption = node - .getDidDocument( - node_api.GetDidDocumentRequest( - did = issuerDID.getValue - ) - ) - .document - val didDocument = didDocumentOption.value - - // get verification key - val issuancekeyProtoOption = - didDocument.publicKeys.find(_.id == issuanceKeyId) - val issuancekeyData = issuancekeyProtoOption.value - val issuanceKey = - issuancekeyProtoOption.flatMap(ProtoCodecs.fromProtoKey).value - val issuanceKeyAddedOn = ProtoCodecs.fromTimestampInfoProto( - issuancekeyData.addedOn.value.timestampInfo.value - ) - val issuanceKeyRevokedOn = - issuancekeyData.revokedOn.flatMap( - _.timestampInfo.map(ProtoCodecs.fromTimestampInfoProto) - ) - - val keyData = KeyData(issuanceKey, issuanceKeyAddedOn, issuanceKeyRevokedOn) - - // request specific credential revocation status to the node - val credentialHash = credential.hash - val credentialRevocationTimeResponse = node.getCredentialRevocationTime( - node_api - .GetCredentialRevocationTimeRequest() - .withBatchId(batchId.getId) - .withCredentialHash(ByteString.copyFrom(credentialHash.getValue)) - ) - val credentialRevocationTime = - credentialRevocationTimeResponse.getRevocationLedgerData.timestampInfo - .map(ProtoCodecs.fromTimestampInfoProto) - - CredVerification - .verify( - keyData, - batchData, - credentialRevocationTime, - merkleRoot, - merkleProof, - credential - ) - } - - private def publicKeyToProto(key: ECPublicKey): node_models.ECKeyData = { - val point = key.getCurvePoint - node_models.ECKeyData( - curve = ECConfig.getCURVE_NAME, - x = ByteString.copyFrom(point.getX.bytes()), - y = ByteString.copyFrom(point.getY.bytes()) - ) - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/Connector.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/Connector.scala deleted file mode 100644 index 5740d34f9..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/Connector.scala +++ /dev/null @@ -1,32 +0,0 @@ -package io.iohk.atala.prism.node.poc.batch - -import io.iohk.atala.prism.crypto.MerkleInclusionProof -import io.iohk.atala.prism.protos.node_api -import io.iohk.atala.prism.protos.node_api.ScheduleOperationsRequest -import io.iohk.atala.prism.protos.node_models.{OperationOutput, SignedAtalaOperation} - -case class Connector(node: node_api.NodeServiceGrpc.NodeServiceBlockingStub) { - def registerDID( - signedAtalaOperation: SignedAtalaOperation - ): OperationOutput = { - node - .scheduleOperations( - ScheduleOperationsRequest(List(signedAtalaOperation)) - ) - .outputs - .head - } - - // a tiny simulation of sending the credential - private var credentialBatchChannel: List[(String, MerkleInclusionProof)] = Nil - - def sendCredentialAndProof( - message: List[(String, MerkleInclusionProof)] - ): Unit = { - credentialBatchChannel = message - } - - def receivedCredentialAndProof(): List[(String, MerkleInclusionProof)] = { - credentialBatchChannel - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/FlowPoC.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/FlowPoC.scala deleted file mode 100644 index 8682ad6b1..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/FlowPoC.scala +++ /dev/null @@ -1,297 +0,0 @@ -package io.iohk.atala.prism.node.poc.batch - -import cats.effect.IO -import cats.effect.unsafe.implicits.global -import cats.scalatest.ValidatedValues.convertValidatedToValidatable -import cats.syntax.functor._ -import com.google.protobuf.ByteString -import io.grpc.inprocess.{InProcessChannelBuilder, InProcessServerBuilder} -import io.grpc.{ManagedChannel, Server} -import io.iohk.atala.prism.node.AtalaWithPostgresSpec -import io.iohk.atala.prism.api.CredentialBatches -import io.iohk.atala.prism.credentials.CredentialBatchId -import io.iohk.atala.prism.credentials.json.JsonBasedCredential -import io.iohk.atala.prism.crypto.{Sha256, Sha256Digest} -import io.iohk.atala.prism.identity.PrismDid.{getDEFAULT_MASTER_KEY_ID => masterKeyId} -import io.iohk.atala.prism.identity.{PrismDid => DID} -import io.iohk.atala.prism.node.logging.TraceId.IOWithTraceIdContext -import io.iohk.atala.prism.node.models.DidSuffix -import io.iohk.atala.prism.node.operations.ApplyOperationConfig -import io.iohk.atala.prism.node.poc.CredVerification.VerificationError._ -import io.iohk.atala.prism.node.poc.{GenericCredentialsSDK, Wallet} -import io.iohk.atala.prism.node.repositories._ -import io.iohk.atala.prism.node.services.models.AtalaObjectNotification -import io.iohk.atala.prism.node.services._ -import io.iohk.atala.prism.node.{DataPreparation, NodeGrpcServiceImpl, UnderlyingLedger} -import io.iohk.atala.prism.protos.node_api -import io.iohk.atala.prism.node.utils.IOUtils._ -import io.iohk.atala.prism.node.utils.NodeClientUtils._ -import org.scalatest.BeforeAndAfterEach -import tofu.logging.Logs -import java.util.concurrent.TimeUnit -import scala.concurrent.duration.DurationInt -import scala.jdk.CollectionConverters._ - -class FlowPoC extends AtalaWithPostgresSpec with BeforeAndAfterEach { - - private val flowPocTestLogs = Logs.withContext[IO, IOWithTraceIdContext] - protected var serverName: String = _ - protected var serverHandle: Server = _ - protected var channelHandle: ManagedChannel = _ - protected var nodeServiceStub: node_api.NodeServiceGrpc.NodeServiceBlockingStub = _ - protected var didDataRepository: DIDDataRepository[IOWithTraceIdContext] = _ - protected var atalaOperationsRepository: AtalaOperationsRepository[IOWithTraceIdContext] = _ - protected var credentialBatchesRepository: CredentialBatchesRepository[IOWithTraceIdContext] = _ - protected var atalaReferenceLedger: UnderlyingLedger[IOWithTraceIdContext] = _ - protected var blockProcessingService: BlockProcessingServiceImpl = _ - protected var objectManagementService: ObjectManagementService[IOWithTraceIdContext] = _ - protected var submissionService: SubmissionService[IOWithTraceIdContext] = _ - protected var submissionSchedulingService: SubmissionSchedulingService = _ - protected var atalaObjectsTransactionsRepository: AtalaObjectsTransactionsRepository[IOWithTraceIdContext] = _ - protected var metricsCountersRepository: MetricsCountersRepository[IOWithTraceIdContext] = _ - protected var keyValuesRepository: KeyValuesRepository[IOWithTraceIdContext] = - _ - protected var protocolVersionsRepository: ProtocolVersionRepository[IOWithTraceIdContext] = _ - private val publicKeysLimit = 10 - private val servicesLimit = 10 - - override def beforeEach(): Unit = { - super.beforeEach() - - didDataRepository = DIDDataRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - credentialBatchesRepository = CredentialBatchesRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - protocolVersionsRepository = ProtocolVersionRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - - atalaReferenceLedger = InMemoryLedgerService.unsafe(onAtalaReference, flowPocTestLogs) - blockProcessingService = new BlockProcessingServiceImpl(ApplyOperationConfig(DidSuffix("0a1e3"))) - atalaOperationsRepository = AtalaOperationsRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - metricsCountersRepository = MetricsCountersRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - atalaObjectsTransactionsRepository = AtalaObjectsTransactionsRepository - .unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - submissionService = SubmissionService.unsafe( - atalaReferenceLedger, - atalaOperationsRepository, - atalaObjectsTransactionsRepository, - logs = flowPocTestLogs - ) - // this service needs to pull operations from the database and to send them to the ledger - submissionSchedulingService = SubmissionSchedulingService( - SubmissionSchedulingService.Config( - refreshAndSubmitPeriod = 1.second, - moveScheduledToPendingPeriod = 2.second - ), - submissionService - ) - keyValuesRepository = KeyValuesRepository.unsafe(dbLiftedToTraceIdIO, flowPocTestLogs) - objectManagementService = ObjectManagementService.unsafe( - atalaOperationsRepository, - atalaObjectsTransactionsRepository, - keyValuesRepository, - protocolVersionsRepository, - blockProcessingService, - publicKeysLimit, - servicesLimit, - dbLiftedToTraceIdIO, - flowPocTestLogs - ) - def onAtalaReference( - notification: AtalaObjectNotification - ): IOWithTraceIdContext[Unit] = objectManagementService - .saveObject(notification) - .void - - serverName = InProcessServerBuilder.generateName() - - serverHandle = InProcessServerBuilder - .forName(serverName) - .directExecutor() - .addService( - node_api.NodeServiceGrpc - .bindService( - new NodeGrpcServiceImpl( - NodeService.unsafe( - didDataRepository, - objectManagementService, - credentialBatchesRepository, - flowPocTestLogs - ) - ), - executionContext - ) - ) - .build() - .start() - - channelHandle = InProcessChannelBuilder.forName(serverName).directExecutor().build() - - nodeServiceStub = node_api.NodeServiceGrpc.blockingStub(channelHandle) - } - - override def afterEach(): Unit = { - channelHandle.shutdown() - channelHandle.awaitTermination(10, TimeUnit.SECONDS) - serverHandle.shutdown() - serverHandle.awaitTermination() - super.afterEach() - } - - "The batch issuance/verification flow" should { - "work" in { - - // the idea of the flow to implement - // 1. issuer generates a DID with the wallet - // 2- she uses the connector to publish it - // 3. she grabs credential data from the management console - // 4- she builds 4 generic credentials - // 5. she signs them with the wallet - // 6. she issues the credentials as two batches (with 2 credentials per batch) - // through the management console - // 7. she encodes the credentials and sends them through the connector along with - // the corresponding proofs of inclusion - // ... later ... - // 8. a verifier receives the credentials through the connector - // 9. gives the signed credentials to the wallet to verify them and it succeeds - // ... later ... - // 10. the issuer decides to revoke the first batch - // 11. the issuer decides to revoke the first credential from the second batch - // ... later ... - // 12. the verifier calls the wallet again to verify the credentials - // and the verification fails for all but the second credential of the second batch - - val wallet = Wallet(nodeServiceStub) - val console = ManagementConsole(nodeServiceStub) - val connector = Connector(nodeServiceStub) - - // 1. issuer generates a DID with the wallet - val (didSuffix, createDIDOp) = wallet.generateDID() - - // 2- she uses the connector to publish it - val signedCreateDIDOp = - wallet.signOperation(createDIDOp, masterKeyId, didSuffix) - val registerDIDOperationId = connector - .registerDID(signedAtalaOperation = signedCreateDIDOp) - .getOperationId - DataPreparation.waitConfirmation( - nodeServiceStub, - registerDIDOperationId - ) - - // 3. she grabs credential data from the management console - val consoleCredentials = console.getCredentials(4) - - // 4. she builds 4 generic credentials - val issuanceKeyId = "issuance0" - - val issuerDID = DID.buildCanonical(Sha256Digest.fromHex(didSuffix.value)) - val credentialsToSign = consoleCredentials.map { credential => - GenericCredentialsSDK.buildGenericCredential( - "university-degree", - issuerDID, - issuanceKeyId, - credential.credentialData - ) - } - - // 5. she signs them with the wallet - val signedCredentials = credentialsToSign.map { credentialToSign => - wallet.signCredential(credentialToSign, issuanceKeyId, didSuffix) - } - - // 6. she issues the credentials as two batches (with 2 credentials per batch) - // through the management console - val batch1 = CredentialBatches.batch(signedCredentials.take(2).asJava) - val (root1, proofs1) = (batch1.getRoot, batch1.getProofs.asScala.toList) - val batch2 = CredentialBatches.batch(signedCredentials.drop(2).asJava) - val (root2, proofs2) = (batch2.getRoot, batch2.getProofs.asScala.toList) - - val issueBatch1Op = issueBatchOperation(issuerDID, root1) - val issueBatch2Op = issueBatchOperation(issuerDID, root2) - - val signedIssueBatch1Op = - wallet.signOperation(issueBatch1Op, issuanceKeyId, didSuffix) - val signedIssueBatch2Op = - wallet.signOperation(issueBatch2Op, issuanceKeyId, didSuffix) - val issueCredentialBatchOperationId1 = - console.issueCredentialBatch(signedIssueBatch1Op).getOperationId - val issueCredentialBatchOperationId2 = - console.issueCredentialBatch(signedIssueBatch2Op).getOperationId - DataPreparation.waitConfirmation( - nodeServiceStub, - issueCredentialBatchOperationId1, - issueCredentialBatchOperationId2 - ) - - // 7. she encodes the credentials and sends them through the connector along with - // the corresponding proofs of inclusion - val credentialsToSend = - signedCredentials.zip(proofs1 ++ proofs2).map { case (c, p) => - (c.getCanonicalForm, p) - } - connector.sendCredentialAndProof(credentialsToSend) - - // ... later ... - // 8. a verifier receives the credentials through the connector - val List((c1, p1), (c2, p2), (c3, p3), (c4, p4)) = - connector.receivedCredentialAndProof().map { case (c, p) => - (JsonBasedCredential.fromString(c), p) - } - - // 9. gives the signed credentials to the wallet to verify them and it succeeds - wallet.verifyCredential(c1, p1).isValid mustBe true - wallet.verifyCredential(c2, p2).isValid mustBe true - wallet.verifyCredential(c3, p3).isValid mustBe true - wallet.verifyCredential(c4, p4).isValid mustBe true - - // ... later ... - // 10. the issuer decides to revoke the first batch - val revocationKeyId = "revocation0" - wallet.addRevocationKeyToDid( - revocationKeyId = revocationKeyId, - previousOperationHash = ByteString.copyFrom(Sha256.compute(createDIDOp.toByteArray).getValue), - didSuffix = didSuffix - ) - - val issueBatch1OpHash = Sha256.compute(issueBatch1Op.toByteArray) - val batchId1 = CredentialBatchId.fromBatchData(issuerDID.getSuffix, root1) - val revokeBatch1Op = - revokeCredentialsOperation(issueBatch1OpHash, batchId1) - val signedRevokeBatch1Op = - wallet.signOperation(revokeBatch1Op, revocationKeyId, didSuffix) - val revokeCredentialBatchOperationId = - console.revokeCredentialBatch(signedRevokeBatch1Op).getOperationId - - // 11. the issuer decides to revoke the first credential from the second batch - val issueBatch2OpHash = Sha256.compute(issueBatch2Op.toByteArray) - val batchId2 = CredentialBatchId.fromBatchData(issuerDID.getSuffix, root2) - val revokeC3Op = - revokeCredentialsOperation(issueBatch2OpHash, batchId2, Seq(c3.hash)) - val signedRevokeC3Op = - wallet.signOperation(revokeC3Op, revocationKeyId, didSuffix) - val revokeSpecificCredentialsOperationId = - console.revokeSpecificCredentials(signedRevokeC3Op).getOperationId - - DataPreparation.waitConfirmation( - nodeServiceStub, - revokeCredentialBatchOperationId, - revokeSpecificCredentialsOperationId - ) - - // ... later ... - // 12. the verifier calls the wallet again to verify the credentials - // and the verification fails for all but the second credential of the second batch - val e1 = wallet.verifyCredential(c1, p1).invalid.e - e1.size mustBe 1 - e1.head mustBe a[BatchWasRevoked] - - val e2 = wallet.verifyCredential(c2, p2).invalid.e - e2.size mustBe 1 - e2.head mustBe a[BatchWasRevoked] - - val e3 = wallet.verifyCredential(c3, p3).invalid.e - e3.size mustBe 1 - e3.head mustBe a[CredentialWasRevoked] - - wallet.verifyCredential(c4, p4).isValid mustBe true - } - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/ManagementConsole.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/ManagementConsole.scala deleted file mode 100644 index 1fcbbf0ce..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/batch/ManagementConsole.scala +++ /dev/null @@ -1,71 +0,0 @@ -package io.iohk.atala.prism.node.poc.batch - -import java.time.LocalDate -import java.util.UUID -import io.iohk.atala.prism.protos.console_models.CManagerGenericCredential -import io.iohk.atala.prism.protos.node_api.ScheduleOperationsRequest -import io.iohk.atala.prism.protos.{node_api, node_models} -import io.iohk.atala.prism.protos.node_models.SignedAtalaOperation - -case class ManagementConsole( - node: node_api.NodeServiceGrpc.NodeServiceBlockingStub -) { - // example credentials we have from the backend - def getCredentials( - amountOfCredentials: Int - ): List[CManagerGenericCredential] = - (1 to amountOfCredentials).toList.map { index => - CManagerGenericCredential( - credentialId = UUID.randomUUID().toString, - issuerId = UUID.randomUUID().toString, - contactId = UUID.randomUUID().toString, - credentialData = s"""{ - | "title" : "Bs in Computer Science", - | "enrollmentDate" : "${LocalDate.now()}", - | "graduationDate" : "${LocalDate.now()}", - | "subjectName" : "Asymptomatic Joe $index" - |}""".stripMargin, - issuerName = "National University of Rosario" - ) - } - - // this is a toy API to simulate what the console does - def issueCredentialBatch( - issueCredentialBatchOperation: SignedAtalaOperation - ): node_models.OperationOutput = { - // First some storage stuff to mark a credential as stored - // It then posts the operation to the node - node - .scheduleOperations( - ScheduleOperationsRequest(List(issueCredentialBatchOperation)) - ) - .outputs - .head - } - - def revokeCredentialBatch( - revokeCredentialBatchOperation: SignedAtalaOperation - ): node_models.OperationOutput = { - // First storage stuff - // then, posting things on the blockchain through the node - node - .scheduleOperations( - ScheduleOperationsRequest(List(revokeCredentialBatchOperation)) - ) - .outputs - .head - } - - def revokeSpecificCredentials( - revokeCredentialBatchOperation: SignedAtalaOperation - ): node_models.OperationOutput = { - // First storage stuff - // then, posting things on the blockchain through the node - node - .scheduleOperations( - ScheduleOperationsRequest(List(revokeCredentialBatchOperation)) - ) - .outputs - .head - } -} diff --git a/node/src/test/scala/io/iohk/atala/prism/node/poc/estimations/CardanoFeeEstimator.scala b/node/src/test/scala/io/iohk/atala/prism/node/poc/estimations/CardanoFeeEstimator.scala deleted file mode 100644 index cfb52d226..000000000 --- a/node/src/test/scala/io/iohk/atala/prism/node/poc/estimations/CardanoFeeEstimator.scala +++ /dev/null @@ -1,348 +0,0 @@ -package io.iohk.atala.prism.node.poc.estimations - -import cats.effect.IO -import cats.effect.unsafe.implicits.global -import com.google.protobuf.ByteString -import com.typesafe.config.ConfigFactory -import io.iohk.atala.prism.crypto.{MerkleRoot, Sha256} -import io.iohk.atala.prism.crypto.EC.{INSTANCE => EC} -import io.iohk.atala.prism.crypto.keys.{ECPrivateKey, ECPublicKey} -import io.iohk.atala.prism.crypto.ECConfig.{INSTANCE => ECConfig} -import io.iohk.atala.prism.identity.{CanonicalPrismDid => Canonical, PrismDid => DID} -import io.iohk.atala.prism.node.NodeConfig -import io.iohk.atala.prism.node.cardano.models._ -import io.iohk.atala.prism.node.cardano.wallet.CardanoWalletApiClient -import io.iohk.atala.prism.node.poc.estimations.CardanoFeeEstimator.{Estimation, Issuer, TotalEstimation} -import io.iohk.atala.prism.protos.node_internal.AtalaObject -import io.iohk.atala.prism.protos.node_models.{AtalaOperation, SignedAtalaOperation} -import io.iohk.atala.prism.protos.{node_internal, node_models} -import org.scalatest.OptionValues._ -import org.scalatest.concurrent.ScalaFutures._ -import tofu.logging.Logs -import io.iohk.atala.prism.node.utils.IOUtils._ -import scala.collection.mutable.ListBuffer -import scala.concurrent.duration._ - -/** Estimates the Cardano fees to pay for a given deployment simulation. - * - *

You can run the estimator with `sbt node/test:run` and choosing `CardanoFeeEstimator` from the list. In order to - * do so, make sure you have set the proper environment variables, as suggested here. - */ -class CardanoFeeEstimator( - walletId: WalletId, - paymentAddress: Address, - cardanoWalletApiClient: CardanoWalletApiClient[IO] -) { - // Max number of credentials that can be issued in the same transaction - private val MAX_CREDENTIAL_BATCH_SIZE = 2048 - - private implicit def patienceConfig: PatienceConfig = - PatienceConfig(20.seconds, 50.millis) - - def estimate(issuers: List[Issuer]): TotalEstimation = { - val createDidAtalaObjects = ListBuffer[AtalaObject]() - val issueCredentialBatchAtalaObjects = ListBuffer[AtalaObject]() - issuers.foreach { issuer => - // Create the DID of the issuer - val masterKey = EC.generateKeyPair() - val issuingKey = EC.generateKeyPair() - val did = createDID(s"Issuer ${issuer.id}") - val masterKeyOperation = addMasterKeyOperation(masterKey.getPublicKey) - createDidAtalaObjects += createAtalaObject( - signOperation(masterKeyOperation, masterKey.getPrivateKey), - signOperation( - addIssuingKeyOperation( - did, - issuingKey.getPublicKey, - masterKeyOperation - ), - masterKey.getPrivateKey - ) - ) - - // Issue credentials - issuer.credentialsToIssue.foreach { credentialsToIssue => - val batches = math - .ceil(credentialsToIssue / MAX_CREDENTIAL_BATCH_SIZE.toDouble) - .toInt - for (batchId <- 0 until batches) { - val merkleRoot = new MerkleRoot( - Sha256.compute(s"Issuer ${issuer.id}, batch $batchId".getBytes) - ) - issueCredentialBatchAtalaObjects += createAtalaObject( - signOperation( - issueCredentialBatchOperation(merkleRoot, did), - issuingKey.getPrivateKey - ) - ) - } - } - } - - TotalEstimation( - didCreation = Estimation( - transactions = createDidAtalaObjects.size, - fees = estimateFees(createDidAtalaObjects) - ), - credentialIssuing = Estimation( - transactions = issueCredentialBatchAtalaObjects.size, - fees = estimateFees(issueCredentialBatchAtalaObjects) - ) - ) - } - - private def estimateFees(atalaObjects: Iterable[AtalaObject]): Lovelace = { - val atalaObjectsBySize = atalaObjects.groupBy(_.toByteArray.length) - val fees = atalaObjectsBySize.foldLeft(BigInt(0)) { case (sum, (_, atalaObjectsWithSameSize)) => - // For performance, use an arbitrary object to estimate all of the objects with the same size, even though they - // may get different fees - sum + atalaObjectsWithSameSize.size * estimateFee( - atalaObjectsWithSameSize.head - ) - } - Lovelace(fees) - } - - private def estimateFee(atalaObject: AtalaObject): Lovelace = { - val estimatedFee = cardanoWalletApiClient - .estimateTransactionFee( - walletId = walletId, - payments = List(Payment(paymentAddress, Lovelace(1000000))), - metadata = Some(AtalaObjectMetadata.toTransactionMetadata(atalaObject)) - ) - .unsafeToFuture() - .futureValue - .toOption - .value - - // We are only interested in the minimum estimated fee, because the maximum is dynamic and wallet-dependent - estimatedFee.min - } - - private def createAtalaObject( - operations: SignedAtalaOperation* - ): AtalaObject = { - val block = node_internal.AtalaBlock(operations) - AtalaObject().withBlockContent(block) - } - - private def signOperation( - atalaOperation: AtalaOperation, - privateKey: ECPrivateKey - ): SignedAtalaOperation = { - node_models.SignedAtalaOperation( - signedWith = DID.getDEFAULT_MASTER_KEY_ID, - operation = Some(atalaOperation), - signature = ByteString.copyFrom( - EC.signBytes(atalaOperation.toByteArray, privateKey).getData - ) - ) - } - - private def createDID(id: String): Canonical = { - DID.buildCanonical(Sha256.compute(id.getBytes)) - } - - private def addMasterKeyOperation(publicKey: ECPublicKey): AtalaOperation = { - val createDIDOp = node_models.CreateDIDOperation( - didData = Some( - node_models.CreateDIDOperation.DIDCreationData( - publicKeys = Seq( - node_models.PublicKey( - id = DID.getDEFAULT_MASTER_KEY_ID, - usage = node_models.KeyUsage.MASTER_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(publicKey) - ) - ) - ) - ) - ) - ) - - node_models.AtalaOperation(AtalaOperation.Operation.CreateDid(createDIDOp)) - } - - private def addIssuingKeyOperation( - did: Canonical, - publicKey: ECPublicKey, - lastOperation: AtalaOperation - ): AtalaOperation = { - val createDIDOp = node_models.UpdateDIDOperation( - previousOperationHash = ByteString.copyFrom(Sha256.compute(lastOperation.toByteArray).getValue), - id = did.getSuffix, - actions = List( - node_models.UpdateDIDAction( - action = node_models.UpdateDIDAction.Action.AddKey( - node_models.AddKeyAction( - key = Some( - node_models.PublicKey( - id = s"issuing0", - usage = node_models.KeyUsage.ISSUING_KEY, - keyData = node_models.PublicKey.KeyData.EcKeyData( - publicKeyToProto(publicKey) - ) - ) - ) - ) - ) - ) - ) - ) - - node_models.AtalaOperation(AtalaOperation.Operation.UpdateDid(createDIDOp)) - } - - private def issueCredentialBatchOperation( - merkleRoot: MerkleRoot, - issuerDid: Canonical - ): AtalaOperation = { - val issueCredentialOp = node_models.IssueCredentialBatchOperation( - credentialBatchData = Some( - node_models.CredentialBatchData( - issuerDid = issuerDid.getSuffix, - merkleRoot = ByteString.copyFrom(merkleRoot.getHash.getValue) - ) - ) - ) - - node_models.AtalaOperation( - AtalaOperation.Operation.IssueCredentialBatch(issueCredentialOp) - ) - } - - private def publicKeyToProto(key: ECPublicKey): node_models.ECKeyData = { - val point = key.getCurvePoint - node_models.ECKeyData( - curve = ECConfig.getCURVE_NAME, - x = ByteString.copyFrom(point.getX.bytes()), - y = ByteString.copyFrom(point.getY.bytes()) - ) - } -} - -object CardanoFeeEstimator { - case class Issuer(id: Int, credentialsToIssue: List[Int]) - - sealed trait EstimationFormat { - val transactions: Int - val fees: Lovelace - - def toString(indent: String): String = { - val averageFee = Lovelace(fees / transactions) - s"""${indent}Transactions: $transactions - |${indent}Fees: ${fees.asAda} (${fees.asUsd}) - | ${indent}Average fee: ${averageFee.asLovelace} (${averageFee.asUsd}) - |""".stripMargin - } - } - - case class TotalEstimation( - didCreation: Estimation, - credentialIssuing: Estimation - ) extends EstimationFormat { - override val transactions: Int = - didCreation.transactions + credentialIssuing.transactions - override val fees: Lovelace = Lovelace( - didCreation.fees + credentialIssuing.fees - ) - } - - case class Estimation(transactions: Int, fees: Lovelace) extends EstimationFormat - - def main(args: Array[String]): Unit = { - estimateEthiopia() - // Force termination as a hanging thread seems to exist - sys.exit(0) - } - - private def estimateEthiopia(): Unit = { - /* - There are: - - 5M students - - 700K teachers - - 4K schools - Schools will: - - Issue IDs to students - - Report 4 times per year - - Issue a yearly certificate - The National Exam Certificate body will: - - Issue certificates for ~500K students per year (for grades 7, 9, and 11) - - Assumptions: - - Students will use unpublished DIDs - - Teachers do not need public DIDs as they don't issue credentials (the school does) - - School DIDs are batched as part of the deployment - - Students are evenly distributed in schools - */ - val students = 5000000 - val schools = 4000 - val yearlyNationalExamCertificates = 500000 - val studentsPerSchool = students / schools - val yearlyReportsPerStudent = 4 - val yearlyCertificatesPerStudent = 1 - val yearlyCredentialsPerStudent = - yearlyReportsPerStudent + yearlyCertificatesPerStudent - - // Issue `yearlyNationalExamCertificates` credentials once per year - val nationalExamCertBody = - Issuer(id = 0, credentialsToIssue = List(yearlyNationalExamCertificates)) - val schoolIssuers = List.tabulate(schools)(schoolId => - // Issue `studentsPerSchool` credentials `yearlyCredentialsPerStudent` times in a year - Issuer( - id = schoolId, - credentialsToIssue = List.fill(yearlyCredentialsPerStudent)(studentsPerSchool) - ) - ) - - val estimator = createCardanoFeeEstimator() - val estimation = - estimator.estimate(List(nationalExamCertBody) ++ schoolIssuers) - - println(s"""Ethiopia estimation: - | Initial setup (DID creation): - |${estimation.didCreation.toString(" - ")} - | Yearly (credential issuing): - |${estimation.credentialIssuing.toString(" - ")} - | Total: - |${estimation.toString(" - ")} - |""".stripMargin) - } - - private def createCardanoFeeEstimator(): CardanoFeeEstimator = { - - val clientConfig = - NodeConfig.cardanoConfig(ConfigFactory.load().getConfig("cardano")) - val walletId = WalletId.from(clientConfig.walletId).value - val paymentAddress = Address(clientConfig.paymentAddress) - val logs = Logs.sync[IO, IO] - val cardanoWalletApiClient = - CardanoWalletApiClient.unsafe[IO, IO]( - clientConfig.cardanoClientConfig.cardanoWalletConfig, - logs - ) - - new CardanoFeeEstimator(walletId, paymentAddress, cardanoWalletApiClient) - } - - implicit class LovelaceFormat(val amount: Lovelace) { - private val ADA_USD_PRICE = 0.103377 - - private def toAda: Double = { - amount.toDouble / 1000000 - } - - def asLovelace: String = { - f"$amount lovelace" - } - - def asAda: String = { - f"$toAda%.6f ADA" - } - - def asUsd: String = { - f"$$${toAda * ADA_USD_PRICE}%.2f USD" - } - } -}