All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plasmalabs.quivr.api.Verifier.scala Maven / Gradle / Ivy

The newest version!
package org.plasmalabs.quivr.api

import cats._
import cats.data.OptionT
import cats.implicits._
import org.plasmalabs.quivr._
import org.plasmalabs.quivr.runtime.QuivrRuntimeErrors.ValidationError.{
  EvaluationAuthorizationFailed,
  LockedPropositionIsUnsatisfiable,
  MessageAuthorizationFailed,
  UserProvidedInterfaceFailure
}
import org.plasmalabs.quivr.runtime.{DynamicContext, QuivrRuntimeError}
import org.plasmalabs.quivr.models._
import java.nio.charset.StandardCharsets
import org.plasmalabs.crypto.hash.Blake2b256

/**
 * A Verifier evaluates whether a given Proof satisfies a certain Proposition
 */
trait Verifier[F[_], Datum] {

  /**
   * Does the given `proof` satisfy the given `proposition` using the given `data`?
   */
  def evaluate(
    proposition: Proposition,
    proof:       Proof,
    context:     DynamicContext[F, String, Datum]
  ): F[Either[QuivrRuntimeError, Boolean]]
}

object Verifier {

  /**
   * @param tag     an identifier of the Operation
   * @param context the Dynamic evaluation context which should provide an API for retrieving the signable bytes
   * @return an array of bytes that is similar to a "signature" for the proof
   */
  private def evaluateBlake2b256Bind[F[_]: Monad, A](
    tag:         String,
    proof:       Proof,
    proofTxBind: TxBind,
    context:     DynamicContext[F, A, ?]
  ): F[Either[QuivrRuntimeError, Boolean]] = for {
    sb <- context.signableBytes
    verifierTxBind = (new Blake2b256).hash(tag.getBytes(StandardCharsets.UTF_8) ++ sb.value.toByteArray)
    res = Either.cond(
      verifierTxBind sameElements proofTxBind.value.toByteArray,
      true,
      MessageAuthorizationFailed(proof)
    )
  } yield res

  /**
   * Collect the result of verification. Does the proof satisfy the proposition.
   * Both msgResult and evalResult need to indicate success
   *
   * @param proposition The proposition that the proof was verified against
   * @param proof The proof that was verified
   * @param msgResult Result of message validation. Success is denoted by Right(true)
   * @param evalResult Result of proposition and proof evaluation. Success is denoted by Right(_)
   * @return The result of verification. If successful, Right(true). Else Left(QuivrRuntimeError)
   */
  private def collectResult(proposition: Proposition, proof: Proof)(
    msgResult:  Either[QuivrRuntimeError, Boolean],
    evalResult: Either[QuivrRuntimeError, ?]
  ): Either[QuivrRuntimeError, Boolean] = (msgResult, evalResult) match {
    case (Right(true), Right(_)) => Right[QuivrRuntimeError, Boolean](true)
    case _                       => Left[QuivrRuntimeError, Boolean](EvaluationAuthorizationFailed(proposition, proof))
  }

  trait Implicits {

    implicit class PropositionOps(proposition: Proposition) {

      def isSatisfiedBy[F[_], Datum](
        proof: Proof
      )(implicit
        context: DynamicContext[F, String, Datum],
        ev:      Verifier[F, Datum]
      ): F[Either[QuivrRuntimeError, Boolean]] =
        ev.evaluate(proposition, proof, context)
    }

    implicit class ProofOps(proof: Proof) {

      def satisfies[F[_], Datum](
        proposition: Proposition
      )(implicit
        ev:      Verifier[F, Datum],
        context: DynamicContext[F, String, Datum]
      ): F[Either[QuivrRuntimeError, Boolean]] =
        ev.evaluate(proposition, proof, context)
    }
  }

  object implicits extends Implicits

  trait Instances {

    private def lockedVerifier[F[_]: Monad](
      proposition: Proposition.Locked,
      proof:       Proof.Locked,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] =
      // should always fail, the Locked Proposition is unsatisfiable
      Either
        .left[QuivrRuntimeError, Boolean](LockedPropositionIsUnsatisfiable)
        .pure[F]

    private def digestVerifier[F[_]: Monad](
      proposition: Proposition.Digest,
      proof:       Proof.Digest,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withDigest(proposition).pure[F]
      wrappedProof       <- Proof().withDigest(proof).pure[F]
      msgResult          <- Verifier.evaluateBlake2b256Bind(Tokens.Digest, wrappedProof, proof.transactionBind, context)
      verification = DigestVerification(proposition.digest, proof.preimage)
      evalResult <- context.digestVerify(proposition.routine)(verification).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def signatureVerifier[F[_]: Monad](
      proposition: Proposition.DigitalSignature,
      proof:       Proof.DigitalSignature,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withDigitalSignature(proposition).pure[F]
      wrappedProof       <- Proof().withDigitalSignature(proof).pure[F]
      msgResult <- Verifier.evaluateBlake2b256Bind(
        Tokens.DigitalSignature,
        wrappedProof,
        proof.transactionBind,
        context
      )
      signedMessage <- context.signableBytes
      verification = SignatureVerification(
        proposition.verificationKey,
        proof.witness,
        Message(signedMessage.value)
      )
      evalResult <- context.signatureVerify(proposition.routine)(verification).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def heightVerifier[F[_]: Monad](
      proposition: Proposition.HeightRange,
      proof:       Proof.HeightRange,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withHeightRange(proposition).pure[F]
      wrappedProof       <- Proof().withHeightRange(proof).pure[F]
      msgResult <- Verifier.evaluateBlake2b256Bind(Tokens.HeightRange, wrappedProof, proof.transactionBind, context)
      chainHeight <- OptionT(context.heightOf(proposition.chain)).fold[Either[QuivrRuntimeError, Long]](
        Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
      )(Right(_))
      evalResult = chainHeight match {
        case Right(h) =>
          if (proposition.min <= h && h <= proposition.max)
            Right(true)
          else Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
        case Left(e) => Left(e)
      }
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def tickVerifier[F[_]: Monad](
      proposition: Proposition.TickRange,
      proof:       Proof.TickRange,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withTickRange(proposition).pure[F]
      wrappedProof       <- Proof().withTickRange(proof).pure[F]
      msgResult <- Verifier.evaluateBlake2b256Bind(Tokens.TickRange, wrappedProof, proof.transactionBind, context)
      evalResult <- context.currentTick.map(t =>
        if (proposition.min <= t && t <= proposition.max)
          Right(true)
        else Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
      )
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def exactMatchVerifier[F[_]: Monad](
      proposition: Proposition.ExactMatch,
      proof:       Proof.ExactMatch,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withExactMatch(proposition).pure[F]
      wrappedProof       <- Proof().withExactMatch(proof).pure[F]
      msgResult  <- Verifier.evaluateBlake2b256Bind(Tokens.ExactMatch, wrappedProof, proof.transactionBind, context)
      evalResult <- context.exactMatch(proposition.location, proposition.compareTo.toByteArray).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def lessThanVerifier[F[_]: Monad](
      proposition: Proposition.LessThan,
      proof:       Proof.LessThan,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withLessThan(proposition).pure[F]
      wrappedProof       <- Proof().withLessThan(proof).pure[F]
      msgResult  <- Verifier.evaluateBlake2b256Bind(Tokens.LessThan, wrappedProof, proof.transactionBind, context)
      evalResult <- context.lessThan(proposition.location, BigInt(proposition.compareTo.value.toByteArray)).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def greaterThanVerifier[F[_]: Monad](
      proposition: Proposition.GreaterThan,
      proof:       Proof.GreaterThan,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withGreaterThan(proposition).pure[F]
      wrappedProof       <- Proof().withGreaterThan(proof).pure[F]
      msgResult  <- Verifier.evaluateBlake2b256Bind(Tokens.GreaterThan, wrappedProof, proof.transactionBind, context)
      evalResult <- context.greaterThan(proposition.location, BigInt(proposition.compareTo.value.toByteArray)).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def equalToVerifier[F[_]: Monad](
      proposition: Proposition.EqualTo,
      proof:       Proof.EqualTo,
      context:     DynamicContext[F, String, ?]
    ): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withEqualTo(proposition).pure[F]
      wrappedProof       <- Proof().withEqualTo(proof).pure[F]
      msgResult  <- Verifier.evaluateBlake2b256Bind(Tokens.LessThan, wrappedProof, proof.transactionBind, context)
      evalResult <- context.equalTo(proposition.location, BigInt(proposition.compareTo.value.toByteArray)).value
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res

    private def thresholdVerifier[F[_]: Monad, Datum](
      proposition: Proposition.Threshold,
      proof:       Proof.Threshold,
      context:     DynamicContext[F, String, Datum]
    )(implicit verifier: Verifier[F, Datum]): F[Either[QuivrRuntimeError, Boolean]] =
      for {
        wrappedProposition <- Proposition().withThreshold(proposition).pure[F]
        wrappedProof       <- Proof().withThreshold(proof).pure[F]
        msgResult <- Verifier.evaluateBlake2b256Bind(Tokens.Threshold, wrappedProof, proof.transactionBind, context)
        evalResult <-
          if (proposition.threshold === 0) Right(true).pure[F]
          else if (proposition.threshold > proposition.challenges.size)
            Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof)).pure[F]
          else if (proof.responses.isEmpty)
            Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof)).pure[F]
          // We assume a one-to-one pairing of sub-proposition to sub-proof with the assumption that some of the proofs
          // may be Proofs.False
          else if (proof.responses.size =!= proposition.challenges.size)
            Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof)).pure[F]
          else {
            proposition.challenges.toList
              .zip(proof.responses)
              .foldLeftM(0L) {
                case (successCount, _) if successCount >= proposition.threshold =>
                  successCount.pure[F]
                case (successCount, (_, proof)) if proof.value.isEmpty =>
                  successCount.pure[F]
                case (successCount, (prop: Proposition, proof)) =>
                  verifier.evaluate(prop, proof, context).map {
                    case Right(true) => (successCount + 1)
                    case _           => successCount
                  }
              }
              .map(_ >= proposition.threshold)
              .map(b => if (b) Right(true) else Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof)))
          }
        res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
      } yield res

    private def notVerifier[F[_]: Monad, Datum](
      proposition: Proposition.Not,
      proof:       Proof.Not,
      context:     DynamicContext[F, String, Datum]
    )(implicit verifier: Verifier[F, Datum]): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withNot(proposition).pure[F]
      wrappedProof       <- Proof().withNot(proof).pure[F]
      msgResult          <- Verifier.evaluateBlake2b256Bind(Tokens.Not, wrappedProof, proof.transactionBind, context)
      evalResult         <- verifier.evaluate(proposition.proposition, proof.proof, context)
      res = collectResult(wrappedProposition, wrappedProof)(msgResult, evalResult)
    } yield res match {
      case Right(true) => Left(EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
      case Left(EvaluationAuthorizationFailed(_, _)) => Right(true)
      case _                                         => Left(UserProvidedInterfaceFailure) // pattern matching warnings.
    }

    private def andVerifier[F[_]: Monad, Datum](
      proposition: Proposition.And,
      proof:       Proof.And,
      context:     DynamicContext[F, String, Datum]
    )(implicit verifier: Verifier[F, Datum]): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withAnd(proposition).pure[F]
      wrappedProof       <- Proof().withAnd(proof).pure[F]
      msgResult          <- Verifier.evaluateBlake2b256Bind(Tokens.And, wrappedProof, proof.transactionBind, context)
      aResult            <- verifier.evaluate(proposition.left, proof.left, context)
      bResult            <- verifier.evaluate(proposition.right, proof.right, context)
      res = (msgResult, aResult, bResult) match {

        case (Right(true), Right(_), Right(_)) => Right[QuivrRuntimeError, Boolean](true)
        case (_, aError, Right(_))             => aError
        case (_, Right(_), bError)             => bError
        case _ => Left[QuivrRuntimeError, Boolean](EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
      }
    } yield res

    private def orVerifier[F[_]: Monad, Datum](
      proposition: Proposition.Or,
      proof:       Proof.Or,
      context:     DynamicContext[F, String, Datum]
    )(implicit verifier: Verifier[F, Datum]): F[Either[QuivrRuntimeError, Boolean]] = for {
      wrappedProposition <- Proposition().withOr(proposition).pure[F]
      wrappedProof       <- Proof().withOr(proof).pure[F]
      msgResult          <- Verifier.evaluateBlake2b256Bind(Tokens.Or, wrappedProof, proof.transactionBind, context)
      aResult            <- verifier.evaluate(proposition.left, proof.left, context)
      bResult            <- verifier.evaluate(proposition.right, proof.right, context)
      res = (msgResult, aResult, bResult) match {
        case (Right(true), Right(_), _) => Right[QuivrRuntimeError, Boolean](true)
        case (Right(true), _, Right(_)) => Right[QuivrRuntimeError, Boolean](true)
        case (_, aError, Right(_))      => aError
        case (_, Right(_), bError)      => bError
        case _ => Left[QuivrRuntimeError, Boolean](EvaluationAuthorizationFailed(wrappedProposition, wrappedProof))
      }
    } yield res

    implicit def verifierInstance[F[_]: Monad, Datum]: Verifier[F, Datum] =
      new Verifier[F, Datum] {

        implicit private val v: Verifier[F, Datum] = this

        def evaluate(
          proposition: Proposition,
          proof:       Proof,
          context:     DynamicContext[F, String, Datum]
        ): F[Either[QuivrRuntimeError, Boolean]] =
          (proposition.value, proof.value) match {
            case (Proposition.Value.Locked(c), Proof.Value.Locked(r)) =>
              lockedVerifier(c, r, context)
            case (Proposition.Value.Digest(c), Proof.Value.Digest(r)) =>
              digestVerifier(c, r, context)
            case (Proposition.Value.DigitalSignature(c), Proof.Value.DigitalSignature(r)) =>
              signatureVerifier(c, r, context)
            case (Proposition.Value.HeightRange(c), Proof.Value.HeightRange(r)) =>
              heightVerifier(c, r, context)
            case (Proposition.Value.TickRange(c), Proof.Value.TickRange(r)) =>
              tickVerifier(c, r, context)
            case (Proposition.Value.ExactMatch(c), Proof.Value.ExactMatch(r)) =>
              exactMatchVerifier(c, r, context)
            case (Proposition.Value.LessThan(c), Proof.Value.LessThan(r)) =>
              lessThanVerifier(c, r, context)
            case (Proposition.Value.GreaterThan(c), Proof.Value.GreaterThan(r)) =>
              greaterThanVerifier(c, r, context)
            case (Proposition.Value.EqualTo(c), Proof.Value.EqualTo(r)) =>
              equalToVerifier(c, r, context)
            case (Proposition.Value.Threshold(c), Proof.Value.Threshold(r)) =>
              thresholdVerifier(c, r, context)
            case (Proposition.Value.Not(c), Proof.Value.Not(r)) =>
              notVerifier(c, r, context)
            case (Proposition.Value.And(c), Proof.Value.And(r)) =>
              andVerifier(c, r, context)
            case (Proposition.Value.Or(c), Proof.Value.Or(r)) =>
              orVerifier(c, r, context)
            // Proposition and Proof are not of the same type or either are Empty => Authorization fails
            case _ => Either.left[QuivrRuntimeError, Boolean](EvaluationAuthorizationFailed(proposition, proof)).pure[F]
          }
      }
  }

  object instances extends Instances
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy