Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
45 changes: 45 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,45 @@
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 round trip") {
val sigmaPropFromAddress = SigmaProp.createFromAddress(address)
// ---- server side ----
val serializedSigmaBoolean = sigmaPropFromAddress.toBytes
// 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 ----

// EIP-28: "the wallet app adds some own bytes to the obtained message from ErgoAuthRequest"
val signedMessage = new String(Random.randomBytes(16)) + requestedMessage +
new String(Random.randomBytes(32))
val signature = new ColdErgoClient(address.getNetworkType, Parameters.ColdClientMaxBlockCost)
.execute { ctx: BlockchainContext =>

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

// ---- transferred to server... ----
ErgoAuthUtils.verifyResponse(sigmaPropFromAddress,
requestedMessage, signedMessage, signature) shouldBe true

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

}
18 changes: 8 additions & 10 deletions appkit/src/test/scala/org/ergoplatform/appkit/TxBuilderSpec.scala
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 @@ -74,16 +73,17 @@ class TxBuilderSpec extends PropSpec with Matchers
new File("storage/E1.json").getPath, "abc")
.build

val signedMessage = proverA.signMessage(proverA.getP2PKAddress,
val proverASigmaPropFromAddress = SigmaProp.createFromAddress(proverA.getAddress)
val signedMessage = proverA.signMessage(proverASigmaPropFromAddress,
msg.getBytes, HintsBag.empty)

Signature.verifySignature(proverA.getP2PKAddress,
Signature.verifySignature(proverASigmaPropFromAddress,
msg.getBytes, signedMessage) shouldBe true

Signature.verifySignature(proverA.getP2PKAddress,
Signature.verifySignature(proverASigmaPropFromAddress,
msg.getBytes, signedMessage) shouldBe true

Signature.verifySignature(proverB.getP2PKAddress,
Signature.verifySignature(SigmaProp.createFromAddress(proverB.getAddress),
msg.getBytes, signedMessage) shouldBe false
}
}
Expand Down Expand Up @@ -290,10 +290,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
22 changes: 22 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.ergoplatform.wallet.secrets.ExtendedPublicKey;
import org.ergoplatform.wallet.secrets.ExtendedSecretKey;

import scala.MatchError;
import scala.util.Try;
import scorex.util.encode.Base58;
import sigmastate.Values;
Expand Down Expand Up @@ -129,6 +130,27 @@ public ErgoContract toErgoContract() {
return new ErgoTreeContract(getErgoAddress().script(), getNetworkType());
}

/**
* @return true if this address is a SigmaBoolean
*/
public boolean isSigmaBoolean() {
try {
getSigmaBoolean();
return true;
} catch (MatchError me) {
return false;
}
}

/**
* @return SigmaBoolean value of this address. Throws an error if
* {@link #isSigmaBoolean()} is false
*/
public Values.SigmaBoolean getSigmaBoolean() {
Values.ErgoTree ergoTree = getErgoAddress().script();
return JavaHelpers$.MODULE$.toSigmaBoolean(ergoTree);
}

/**
* Create Ergo Address from base58 string.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,30 @@ package org.ergoplatform.appkit
import org.ergoplatform.validation.ValidationRules
import org.ergoplatform.wallet.interpreter.ErgoInterpreter
import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput}

import java.util
import java.util.{List => JList}

import org.ergoplatform.ErgoBox.TokenId
import org.ergoplatform.wallet.secrets.ExtendedSecretKey
import sigmastate.basics.{SigmaProtocol, SigmaProtocolPrivateInput, SigmaProtocolCommonInput, DiffieHellmanTupleProverInput}
import sigmastate.basics.{SigmaProtocolCommonInput, DiffieHellmanTupleProverInput, SigmaProtocol, SigmaProtocolPrivateInput}
import org.ergoplatform._
import org.ergoplatform.utils.ArithUtils
import org.ergoplatform.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext, TransactionContext}
import org.ergoplatform.wallet.protocol.context.{ErgoLikeStateContext, ErgoLikeParameters, TransactionContext}
import scorex.crypto.authds.ADKey
import sigmastate.Values.{ErgoTree, SigmaBoolean}
import sigmastate.Values.{SigmaBoolean, ErgoTree}

import scala.util.Try
import sigmastate.eval.CompiletimeIRContext
import sigmastate.interpreter.Interpreter.{ReductionResult, ScriptEnv}
import sigmastate.interpreter.{Interpreter, CostedProverResult, ContextExtension, ProverInterpreter, HintsBag}
import sigmastate.eval.{IRContext, CompiletimeIRContext}
import sigmastate.interpreter.Interpreter.{ReductionResult, ScriptEnv, error}
import sigmastate.interpreter.{Interpreter, CostedProverResult, InterpreterContext, PrecompiledScriptProcessor, ContextExtension, ProverInterpreter, HintsBag}
import sigmastate.lang.exceptions.CostLimitException
import sigmastate.serialization.SigmaSerializer
import sigmastate.utxo.CostTable
import sigmastate.utils.Helpers._
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import sigmastate.utils.{SigmaByteWriter, SigmaByteReader}
import spire.syntax.all.cfor
import scalan.util.Extensions.LongOps

import scala.collection.mutable

object Helpers {
Expand All @@ -34,6 +35,8 @@ object Helpers {
}
}



/**
* A class which holds secrets and can sign transactions (aka generate proofs).
*
Expand Down
30 changes: 30 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,30 @@
package org.ergoplatform.appkit;

import java.nio.charset.StandardCharsets;

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

/**
* Verifies an ErgoAuthResponse
*
* @param sigmaProp the Sigma proposition 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(SigmaProp sigmaProp, String originalMessage,
String signedMessage, byte[] signature) {

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

return Signature.verifySignature(sigmaProp,
signedMessage.getBytes(StandardCharsets.UTF_8),
signature);
}
}
4 changes: 2 additions & 2 deletions common/src/main/java/org/ergoplatform/appkit/ErgoType.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ErgoType<T> {
private static ErgoType<scala.Boolean> _boolean = new ErgoType(RType.BooleanType());
private static ErgoType<BigInt> _bigInt = new ErgoType(JavaHelpers.BigIntRType());
private static ErgoType<GroupElement> _groupElement = new ErgoType(JavaHelpers.GroupElementRType());
private static ErgoType<SigmaProp> _sigmaProp = new ErgoType(JavaHelpers.SigmaPropRType());
private static ErgoType<special.sigma.SigmaProp> _sigmaProp = new ErgoType(JavaHelpers.SigmaPropRType());
private static ErgoType<AvlTree> _avlTree = new ErgoType(JavaHelpers.AvlTreeRType());
private static ErgoType<Box> _box = new ErgoType(JavaHelpers.BoxRType());
private static ErgoType<Header> _header = new ErgoType(JavaHelpers.HeaderRType());
Expand Down Expand Up @@ -63,7 +63,7 @@ public boolean equals(Object obj) {

static public ErgoType<GroupElement> groupElementType() { return _groupElement; }

static public ErgoType<SigmaProp> sigmaPropType() { return _sigmaProp; }
static public ErgoType<special.sigma.SigmaProp> sigmaPropType() { return _sigmaProp; }

static public ErgoType<AvlTree> avlTreeType() { return _avlTree; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ static public ErgoValue<GroupElement> of(GroupElement ge) {
return new ErgoValue<>(ge, ErgoType.groupElementType());
}

static public ErgoValue<SigmaProp> of(Values.SigmaBoolean value) {
static public ErgoValue<special.sigma.SigmaProp> of(Values.SigmaBoolean value) {
return new ErgoValue<>(JavaHelpers.SigmaDsl().SigmaProp(value), ErgoType.sigmaPropType());
}

Expand Down
23 changes: 21 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, SigmaPropConstant}
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 Expand Up @@ -309,6 +321,13 @@ object JavaHelpers {
CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes)
}

def toSigmaBoolean(ergoTree: ErgoTree): SigmaBoolean = {
val prop = ergoTree.toProposition(ergoTree.isConstantSegregation)
prop match {
case SigmaPropConstant(p) => SigmaDsl.toSigmaBoolean(p)
}
}

def getStateDigest(tree: AvlTree): Array[Byte] = {
tree.digest.toArray
}
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;
}
39 changes: 39 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/SigmaProp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.ergoplatform.appkit;

import sigmastate.Values;

/**
* Proposition which can be proven and verified by sigma protocol.
*/
public class SigmaProp {
private final Values.SigmaBoolean sigmaBoolean;

public SigmaProp(Values.SigmaBoolean sigmaBoolean) {
this.sigmaBoolean = sigmaBoolean;
}

public Values.SigmaBoolean getSigmaBoolean() {
return sigmaBoolean;
}

/**
* Serializes this SigmaProp.
*/
public byte[] toBytes() {
return Iso.isoSigmaBooleanToByteArray().to(sigmaBoolean);
}

/**
* @return SigmaProp equal to the one that was serialized with {@link #toBytes()}
*/
public static SigmaProp parseFromBytes(byte[] serializedBytes) {
return new SigmaProp(Iso.isoSigmaBooleanToByteArray().from(serializedBytes));
}

/**
* @return SigmaProp from Address. Note that only SigmaBoolean addresses can be used
*/
public static SigmaProp createFromAddress(Address address) {
return new SigmaProp(address.getSigmaBoolean());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.ergoplatform.appkit

import sigmastate.eval.IRContext
import sigmastate.interpreter.Interpreter.error
import sigmastate.interpreter.{PrecompiledScriptProcessor, Interpreter, InterpreterContext}

/** Simple light-weight interpreter that don't require IRContext and hence cannot perform
* script reduction, but it can however verify sigma-protocol propositions [[SigmaProp]].
*/
object SigmaPropInterpreter extends Interpreter {
override type CTX = InterpreterContext
override val IR: IRContext = null
override def precompiledScriptProcessor: PrecompiledScriptProcessor =
error("Script reduction is not supported")
}
Loading