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

sss.openstar.controller.EncryptedMessageHelper.scala Maven / Gradle / Ivy

package sss.openstar.controller

import sss.ancillary.{Guid, Logging}
import sss.openstar.Currency
import sss.openstar.account.NodeSigner.KeyType
import sss.openstar.account.{NodeIdentity, NodeVerifier}
import sss.openstar.balanceledger.TxIndex
import sss.openstar.chains.TxWriterActor.{InternalCommit, InternalTempNack}
import sss.openstar.contract.SaleOrReturnSecretEnc
import sss.openstar.controller.SendMessage.SubmitMessage
import sss.openstar.crypto.SeedBytes
import sss.openstar.identityledger.IdentityServiceQuery
import sss.openstar.message.payloads.MessageEcryption
import sss.openstar.message.payloads.MessageEcryption.{EmbeddedBounty, MessageAttachments}
import sss.openstar.ui.rpc._
import sss.openstar.wallet.UnlockedWallet
import sss.openstar.wallet.Wallet.CreditTooLow

import scala.concurrent.{ExecutionContext, Future}


/**
  * Created by alan on 6/10/16.
  */
object EncryptedMessageHelper extends Logging {

  // The storage providers should be claiming the storage reward immediately
  private val numBlocksForProviderToClaimWithin = 2

  private def toVerifier(identityServiceQuery: IdentityServiceQuery,
                         keyType: KeyType,
                                  tos: Seq[String])(implicit ec: ExecutionContext): Future[Seq[NodeVerifier]] = Future {
    tos.map { to =>
      identityServiceQuery.accountOpt(to) match {
        case Some(pkAc) if pkAc.typedPublicKey.keyType == keyType =>
          Some(identityServiceQuery.toVerifier((pkAc)))
        case Some(pkAc) =>
          log.info(s"Default keyTypes do not match, sender: ${keyType}, recip: ${pkAc.typedPublicKey.keyType}")
          None
        case None =>
          log.info(s"No such user $to in $tos, dropping message recipient")
          None
      }
    }.collect { case Some(pki) => pki }
  }

  private def setupEmbeddedBounty(userWallet: UnlockedWallet[Currency],
                                  amountValue: Long,
                                  secretOpt: Option[Array[Byte]],
                                  sender: NodeIdentity,
                                  foundTos: Seq[NodeVerifier],
                                  numBlocksToLive:Int)(implicit ec: ExecutionContext): Future[Result[EmbeddedBounty]] = {
    val returnBlockHeight = userWallet.w.currentBlockHeight() + numBlocksToLive
    val amount = userWallet.w.amount(amountValue)
    Future.fromTry(userWallet.w.createTx((amount * foundTos.size).get, numBlocksToLive)).flatMap { baseTx =>

      val paymentsOut = foundTos map { foundTo =>
        userWallet.w.output(amount, SaleOrReturnSecretEnc(sender.id, foundTo.nodeIdTag.nodeId, secretOpt,
          returnBlockHeight))
      }
      val startToPaymentIndex = baseTx.outs.size

      val tx = userWallet.w.appendOutputs(baseTx, paymentsOut: _*)
      userWallet.payAsync(tx).map(r => (tx, startToPaymentIndex, foundTos, returnBlockHeight, r))

    }.map {
      case (tx, startToPaymentIndex, tos, returnBlockHeight, _: InternalCommit) =>

        log.info(s"Successfully committed base tx for $amountValue tos: ${tos.map(_.nodeIdTag.nodeId)} author: ${sender.id}")
        success(
          EmbeddedBounty(
            amountValue,
            returnBlockHeight,
            TxIndex(tx.txId, startToPaymentIndex),
          )
        )

      case (_, _, _, _, _: InternalTempNack) =>
        problem(s"Network busy, try again.")

      case x =>
        log.error(s"Problem paying for message delivery")
        log.error(x.toString)
        problem(s"Problem paying for message delivery, see log.")

    }
  }

  def sendMessageToMany(
                         guid: Guid,
                         text: String,
                         attachments: Option[MessageAttachments],
                         amountValue: Long,
                         tos: Seq[String],
                         numBlocksToLive: Int,
                         parentGuidOpt: Option[Guid]
                       )
                       (implicit userWallet: UnlockedWallet[Currency],
                        identityServiceQuery: IdentityServiceQuery,
                        sender: NodeIdentity,
                        submitMessage: SubmitMessage,
                        ec: ExecutionContext
                       ): Future[Result[Boolean]] = {
    if (tos.isEmpty) Future.successful(problem("Message has no recipients"))
    else if (text.trim.isEmpty && attachments.isEmpty) Future.successful(problem("Message has no body and no attachments"))
    else {
      val receiversMustHaveMatchingKeyType = sender.defaultNodeVerifier.verifier.typedPublicKey.keyType
      toVerifier(identityServiceQuery, receiversMustHaveMatchingKeyType, tos)
    }.flatMap { foundTos =>

      val secret = SeedBytes.secureSeed(16)
      val secretOpt = Option(secret)
      require(foundTos.nonEmpty, s"No valid recipients in ${tos} ")

      (if (amountValue > 0) {
        setupEmbeddedBounty(
          userWallet,
          amountValue,
          secretOpt,
          sender,
          foundTos,
          numBlocksToLive).map(_.map(Some(_)))
      } else {
        Future.successful(success[Option[EmbeddedBounty]](None))
      })
        .flatMap {
          case Left(p) =>
            Future.successful(problem(p))
          case Right(bountyOpt) =>

            val encryptedMessageF = MessageEcryption.encryptWithEmbeddedSecret(
              bountyOpt,
              sender,
              foundTos,
              text,
              attachments,
              secret,
              guid,
              Seq.empty
            )
            encryptedMessageF
              .flatMap(encryptedMessage =>
                submitMessage(
                  sender.id,
                  userWallet,
                  numBlocksForProviderToClaimWithin,
                  foundTos.map(_.nodeIdTag.nodeId).toSet,
                  encryptedMessage,
                  parentGuidOpt,
                  encryptedMessage.guid
                )
              ).recover {
              case _: CreditTooLow => problem(CreditTooLowProblem)
            }.map(_ => ok())
        }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy