Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions appkit/src/main/scala/org/ergoplatform/appkit/ColdErgoClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ package org.ergoplatform.appkit

import java.util.function
import org.ergoplatform.restapi.client
import org.ergoplatform.appkit.impl.{BlockchainContextBuilderImpl, ColdBlockchainContext}
import org.ergoplatform.appkit.impl.{ColdBlockchainContext, NodeInfoParameters}
import org.ergoplatform.restapi.client.NodeInfo

class ColdErgoClient(networkType: NetworkType, params: BlockchainParameters) extends ErgoClient {

/**
* Convenience constructor for giving maxBlockCost
*/
def this(networkType: NetworkType, maxBlockCost: Int) {
this(networkType, new NodeInfoParameters(
new NodeInfo().parameters(new client.Parameters()
.maxBlockCost(Integer.valueOf(maxBlockCost)))))
}

class ColdErgoClient(networkType: NetworkType, params: client.Parameters) extends ErgoClient {
override def execute[T](action: function.Function[BlockchainContext, T]): T = {
val ctx = new ColdBlockchainContext(networkType, params)
val res = action.apply(ctx)
Expand Down
41 changes: 41 additions & 0 deletions appkit/src/test/scala/org/ergoplatform/appkit/ErgoAuthSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.ergoplatform.appkit

import org.scalatest.{Matchers, PropSpec}
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
import scorex.util.Random
import sigmastate.interpreter.HintsBag

import java.nio.charset.StandardCharsets

class ErgoAuthSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyChecks
with AppkitTestingCommon {

property("ErgoAuth address roundtrip") {
// server side
val serializedSigmaBoolean = ErgoAuthUtils.serializeSigmaBoolean(address)
Comment thread
aslesarenko marked this conversation as resolved.
Outdated
// message to sign should be something random and not repeating
val requestedMessage = addrStr + System.currentTimeMillis().toString

// transferred to client, and now we are on client side
val signedMessage = new String(Random.randomBytes(16)) + requestedMessage +
new String(Random.randomBytes(32))
Comment thread
aslesarenko marked this conversation as resolved.
val signature = new ColdErgoClient(address.getNetworkType, Parameters.ColdClientMaxBlockCost)
.execute { ctx: BlockchainContext =>

val prover = ctx.newProverBuilder().withMnemonic(mnemonic, SecretString.empty()).build()
prover.signMessage(ErgoAuthUtils.deserializeSigmaBoolean(serializedSigmaBoolean),
signedMessage.getBytes(StandardCharsets.UTF_8),
HintsBag.empty)
}

// transferred to server...
ErgoAuthUtils.verifyResponse(address, requestedMessage, signedMessage, signature) shouldBe (true)
Comment thread
aslesarenko marked this conversation as resolved.
Outdated

// and in case someone wanted to fool us
ErgoAuthUtils.verifyResponse(address,
requestedMessage,
signedMessage,
new Array[Byte](0)) shouldBe (false)
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.ergoplatform.appkit

import org.ergoplatform.appkit.InputBoxesSelectionException.NotEnoughErgsException
import org.ergoplatform.appkit.impl.{BlockchainContextImpl, Eip4TokenBuilder, ErgoTreeContract}
import org.ergoplatform.appkit.impl.{Eip4TokenBuilder, ErgoTreeContract}
import org.ergoplatform.appkit.testing.AppkitTesting
import org.ergoplatform.restapi.client
import org.ergoplatform.{ErgoBox, ErgoScriptPredef}
import org.scalacheck.Gen
import org.scalatest.{Matchers, PropSpec}
Expand Down Expand Up @@ -290,10 +289,8 @@ class TxBuilderSpec extends PropSpec with Matchers

// the only necessary parameter can either be hard-coded or passed
// together with ReducedTransaction
val blockchainParams = new client.Parameters()
.maxBlockCost(Integer.valueOf(1000000))

val coldClient = new ColdErgoClient(NetworkType.MAINNET, blockchainParams)
val maxBlockCost = Parameters.ColdClientMaxBlockCost
val coldClient = new ColdErgoClient(NetworkType.MAINNET, maxBlockCost)

coldClient.execute { ctx: BlockchainContext =>
// test that context is cold
Expand Down
62 changes: 62 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/ErgoAuthUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.ergoplatform.appkit;

import java.nio.charset.StandardCharsets;

import sigmastate.Values.SigmaBoolean;

/**
* Helper utilities for EIP-28 ErgoAuth
*/
public class ErgoAuthUtils {

/**
* Serializes the given SigmaBoolean to be sent in ErgoAuthRequest.
*/
public static byte[] serializeSigmaBoolean(SigmaBoolean sigmaBoolean) {
return Iso.isoSigmaBooleanToByteArray().to(sigmaBoolean);
}

/**
* Converts given address to SigmaBoolean and serializes it to be sent in ErgoAuthRequest.
*/
public static byte[] serializeSigmaBoolean(Address address) {
Comment thread
aslesarenko marked this conversation as resolved.
Outdated
return serializeSigmaBoolean(address.getPublicKey());
}

/**
* Deserializes a SigmaBoolean from byte array
*/
public static SigmaBoolean deserializeSigmaBoolean(byte[] serializedSigmaBoolean) {
return Iso.isoSigmaBooleanToByteArray().from(serializedSigmaBoolean);
}

/**
* Verifies an ErgoAuthResponse
*
* @param sigmaBoolean the SigmaBoolean needed to be fulfilled for signing the message
* @param originalMessage the original message sent in ErgoAuthRequest, needs to be contained
* in signedMessage
* @param signedMessage the message signed by client
* @param signature signature for signedMessage
* @return whether verification is successful
*/
public static boolean verifyResponse(SigmaBoolean sigmaBoolean, String originalMessage,
String signedMessage, byte[] signature) {

if (!signedMessage.contains(originalMessage))
return false;

return Signature.verifySignature(sigmaBoolean,
signedMessage.getBytes(StandardCharsets.UTF_8),
signature);
}

/**
* Verifies an ErgoAuthResponnse for a given address.
* See {@link #verifyResponse(SigmaBoolean, String, String, byte[])} for more information.
*/
public static boolean verifyResponse(Address address, String originalMessage,
String signedMessage, byte[] signature) {
return verifyResponse(address.getPublicKey(), originalMessage, signedMessage, signature);
}
}
16 changes: 14 additions & 2 deletions common/src/main/java/org/ergoplatform/appkit/JavaHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import scala.collection.{mutable, JavaConversions}
import org.ergoplatform._
import org.ergoplatform.ErgoBox.TokenId
import sigmastate.SType
import sigmastate.Values.{ErgoTree, Constant, SValue, EvaluatedValue}
import sigmastate.serialization.{ValueSerializer, ErgoTreeSerializer, SigmaSerializer, GroupElementSerializer}
import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue, SValue, SigmaBoolean}
import sigmastate.serialization.{ErgoTreeSerializer, GroupElementSerializer, SigmaSerializer, ValueSerializer}
import scorex.crypto.authds.ADKey
import scorex.crypto.hash.Digest32
import org.ergoplatform.wallet.mnemonic.{Mnemonic => WMnemonic}
Expand Down Expand Up @@ -181,6 +181,18 @@ object Iso extends LowPriorityIsos {
JListToColl(isoErgoTokenToPair, RType[(TokenId, Long)])
}

val isoSigmaBooleanToByteArray: Iso[SigmaBoolean, Array[Byte]] = new Iso[SigmaBoolean, Array[Byte]] {
override def to(a: SigmaBoolean): Array[Byte] = {
val w = SigmaSerializer.startWriter()
SigmaBoolean.serializer.serialize(a, w)
w.toBytes
}
override def from(b: Array[Byte]): SigmaBoolean ={
val r = SigmaSerializer.startReader(b, 0)
SigmaBoolean.serializer.parse(r)
}
}

implicit val jstringToOptionString: Iso[JString, Option[String]] = new Iso[JString, Option[String]] {
override def to(a: JString): Option[String] = if (Strings.isNullOrEmpty(a)) None else Some(a)
override def from(b: Option[String]): JString = if (b.isEmpty) "" else b.get
Expand Down
5 changes: 5 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/Parameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ public class Parameters {
* and `change` output in not added to the transaction.
*/
public static final long MinChangeValue = 1000 * 1000;

/**
* Max block cost for Cold Client
*/
public static final int ColdClientMaxBlockCost = 1000000;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package org.ergoplatform.appkit.impl

import org.ergoplatform.appkit._

import java.util
import org.ergoplatform.appkit.{Address, BlockchainDataSource, BlockchainParameters, CoveringBoxes, ErgoProverBuilder, ErgoToken, InputBox, NetworkType, PreHeaderBuilder, SignedTransaction, UnsignedTransactionBuilder}
import org.ergoplatform.restapi.client.{NodeInfo, Parameters}

class ColdBlockchainContext(networkType: NetworkType, params: Parameters) extends BlockchainContextBase(networkType) {
class ColdBlockchainContext(networkType: NetworkType, params: BlockchainParameters) extends BlockchainContextBase(networkType) {
override def getDataSource: BlockchainDataSource = throw new UnsupportedOperationException("Cold blockchain context has no data source.")

private val _params = new NodeInfoParameters(new NodeInfo()
.parameters(params))

override def getParameters: BlockchainParameters = _params
override def getParameters: BlockchainParameters = params

override def createPreHeader(): PreHeaderBuilder = throw new UnsupportedOperationException("Cold blockchain context has no pre header builder.")

Expand Down