Skip to content

Commit

Permalink
Merge pull request #68 from ergoplatform/develop
Browse files Browse the repository at this point in the history
Release v4.0.1 (with Sigma v4.0.1 and ergo-wallet v4.0.0)
  • Loading branch information
aslesarenko authored Mar 25, 2021
2 parents 4d9e660 + f3e2361 commit 8c48ffd
Show file tree
Hide file tree
Showing 14 changed files with 374 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import sigmastate.eval._
import sigmastate.interpreter.CryptoConstants
import special.sigma.GroupElement
import JavaHelpers._
import java.util.{List => JList}
import java.util.{Arrays, List => JList}

import org.ergoplatform.appkit.Parameters.MinFee

Expand Down Expand Up @@ -34,20 +34,28 @@ class ChangeOutputSpec extends PropSpec with Matchers
| val gXY = SELF.R5[GroupElement].get
| proveDHTuple(gY, gY, gXY, gXY)
|}""".stripMargin
)).build().convertToInputWith("f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809", 0)
)).build().convertToInputWith(
"f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809",
0)

val txB = ctx.newTxBuilder()
val output = txB.outBoxBuilder().value(15000000).contract(ctx.compileContract(ConstantsBuilder.empty(),"{sigmaProp(true)}")).build()
val inputs = new java.util.ArrayList[InputBox]()
inputs.add(input)
val output = txB.outBoxBuilder()
.value(15000000)
.contract(ctx.compileContract(
ConstantsBuilder.empty(),"{sigmaProp(true)}"))
.build()
val inputs = Arrays.asList(input)

// below is ergoTree of a random box picked from the block explorer.
// The boxId is 02abc29b6a28ccf7e9620afa16e1067caeb75fcd2e62c066e190742962cdcbae
// We just need valid ergoTree to construct the change address
val tree = "100207036ba5cfbc03ea2471fdf02737f64dbcd58c34461a7ec1e586dcd713dacbf89a120400d805d601db6a01ddd6027300d603b2a5730100d604e4c672030407d605e4c672030507eb02ce7201720272047205ce7201720472027205"
val ergoTree = JavaHelpers.decodeStringToErgoTree(tree)
val changeAddr = Address.fromErgoTree(ergoTree, NetworkType.MAINNET).getErgoAddress
val unsigned = txB.boxesToSpend(inputs).outputs(output).fee(1000000).sendChangeTo(changeAddr).build()
val unsigned = txB.boxesToSpend(inputs)
.outputs(output)
.fee(1000000)
.sendChangeTo(changeAddr).build()
// alice signing bob's box. Does not work here but works in other cases.
val signed = ctx.newProverBuilder().withDHTData(gY, gY, gXY, gXY, x).build().sign(unsigned)
val outputs = signed.getOutputsToSpend
Expand Down Expand Up @@ -80,7 +88,8 @@ class ChangeOutputSpec extends PropSpec with Matchers
val txB = ctx.newTxBuilder()
val output = txB.outBoxBuilder()
.value(15000000)
.contract(ctx.compileContract(ConstantsBuilder.empty(),"{sigmaProp(true)}")).build()
.contract(ctx.compileContract(ConstantsBuilder.empty(),"{sigmaProp(true)}"))
.build()
val inputs = new java.util.ArrayList[InputBox]()
inputs.add(input)

Expand All @@ -90,8 +99,14 @@ class ChangeOutputSpec extends PropSpec with Matchers
val tree = "100207036ba5cfbc03ea2471fdf02737f64dbcd58c34461a7ec1e586dcd713dacbf89a120400d805d601db6a01ddd6027300d603b2a5730100d604e4c672030407d605e4c672030507eb02ce7201720272047205ce7201720472027205"
val ergoTree = JavaHelpers.decodeStringToErgoTree(tree)
val changeAddr = Address.fromErgoTree(ergoTree, NetworkType.MAINNET).getErgoAddress
val unsigned = txB.boxesToSpend(inputs).outputs(output).fee(15000000).sendChangeTo(changeAddr).build()
val signed = ctx.newProverBuilder().withDHTData(gY, gY, gXY, gXY, x).build().sign(unsigned) // alice signing bob's box. Does not work here but works in other cases.
val unsigned = txB.boxesToSpend(inputs)
.outputs(output)
.fee(15000000)
.sendChangeTo(changeAddr).build()
val signed = ctx.newProverBuilder()
.withDHTData(gY, gY, gXY, gXY, x)
.build()
.sign(unsigned) // alice signing bob's box. Does not work here but works in other cases.
val outputs = signed.getOutputsToSpend
assert(outputs.size == 2)
println(signed.toJson(false))
Expand All @@ -118,7 +133,8 @@ class ChangeOutputSpec extends PropSpec with Matchers
| val gXY = SELF.R5[GroupElement].get
| proveDHTuple(gY, gY, gXY, gXY)
|}""".stripMargin
)).build().convertToInputWith("f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809", 0)
)).build().convertToInputWith(
"f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809", 0)

val tokenId = input0.getId.toString
val tokenAmount = 5000000000L
Expand All @@ -132,22 +148,29 @@ class ChangeOutputSpec extends PropSpec with Matchers
val tokenBox = txB.outBoxBuilder
.value(15000000) // value of token box, doesn't really matter
.tokens(new ErgoToken(tokenId, tokenAmount)) // amount of token issuing
.contract(ctx.compileContract( // contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(),
"{sigmaProp(1 < 2)}"
.contract(ctx.compileContract(
// contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(), "{sigmaProp(1 < 2)}"
))
.build()

val inputs = new java.util.ArrayList[InputBox]()
inputs.add(input0)

// below is ergoTree of a random box picked from the block explorer. The boxId is 02abc29b6a28ccf7e9620afa16e1067caeb75fcd2e62c066e190742962cdcbae
// below is ergoTree of a random box picked from the block explorer.
// The boxId is 02abc29b6a28ccf7e9620afa16e1067caeb75fcd2e62c066e190742962cdcbae
// We just need valid ergoTree to construct the change address
val tree = "100207036ba5cfbc03ea2471fdf02737f64dbcd58c34461a7ec1e586dcd713dacbf89a120400d805d601db6a01ddd6027300d603b2a5730100d604e4c672030407d605e4c672030507eb02ce7201720272047205ce7201720472027205"
val ergoTree = JavaHelpers.decodeStringToErgoTree(tree)
val changeAddr = Address.fromErgoTree(ergoTree, NetworkType.MAINNET).getErgoAddress
val unsigned = txB.boxesToSpend(inputs).outputs(tokenBox).fee(15000000).sendChangeTo(changeAddr).build()
val signed = ctx.newProverBuilder().withDHTData(gY, gY, gXY, gXY, x).build().sign(unsigned) // alice signing bob's box. Does not work here but works in other cases.
val unsigned = txB.boxesToSpend(inputs)
.outputs(tokenBox)
.fee(15000000)
.sendChangeTo(changeAddr).build()
val signed = ctx.newProverBuilder()
.withDHTData(gY, gY, gXY, gXY, x)
.build()
.sign(unsigned) // alice signing bob's box. Does not work here but works in other cases.
val outputs = signed.getOutputsToSpend
assert(outputs.size == 2)
println(signed.toJson(false))
Expand All @@ -161,7 +184,8 @@ class ChangeOutputSpec extends PropSpec with Matchers
val ergAmountToSend = boxWithToken.getValue - MinFee - expectedChange
val out = txB.outBoxBuilder
.value(ergAmountToSend)
.contract(ctx.compileContract( // contract of the box containing tokens, just has to be spendable
.contract(ctx.compileContract(
// contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(), "{sigmaProp(1 < 2)}"
))
.build()
Expand Down
85 changes: 85 additions & 0 deletions appkit/src/test/scala/org/ergoplatform/appkit/TxBuilderSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.ergoplatform.appkit

import java.math.BigInteger
import java.util.Arrays

import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
import org.scalatest.{PropSpec, Matchers}
import sigmastate.eval.CBigInt
import sigmastate.helpers.NegativeTesting

class TxBuilderSpec extends PropSpec with Matchers
with ScalaCheckDrivenPropertyChecks
with AppkitTesting
with HttpClientTesting
with NegativeTesting {

def createTestInput(ctx: BlockchainContext): InputBox = {
ctx.newTxBuilder.outBoxBuilder
.value(30000000)
.contract(ctx.compileContract(
ConstantsBuilder.empty(),
"""{
| val v1 = getVar[Int](1).get
| val v10 = getVar[BigInt](10).get
| sigmaProp(v1.toBigInt == v10)
|}""".stripMargin))
.build().convertToInputWith(
"f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809",
0)
}

property("ContextVar id should be in range") {
for (id <- 0 to Byte.MaxValue) {
ContextVar.of(id.toByte, 10)
}

def checkFailed(invalidId: Int) = {
assertExceptionThrown(
ContextVar.of(invalidId.toByte, 10),
exceptionLike[IllegalArgumentException]("Context variable id should be in range"),
clue = s"id: $invalidId"
)
}

for (id <- Byte.MinValue to -1) {
checkFailed(id)
}
}

property("InputBox support context variables") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
val contextVars = Seq(
ContextVar.of(1.toByte, 100),
ContextVar.of(10.toByte, CBigInt(BigInteger.valueOf(100)))
)
val input = createTestInput(ctx)
.withContextVars(contextVars:_*)
val txB = ctx.newTxBuilder()
val output = txB.outBoxBuilder()
.value(15000000)
.contract(ctx.compileContract(
ConstantsBuilder.empty(),"{sigmaProp(true)}"))
.build()

val changeAddr = Address.fromErgoTree(input.getErgoTree, NetworkType.MAINNET).getErgoAddress
val unsigned = txB.boxesToSpend(Arrays.asList(input))
.outputs(output)
.fee(1000000)
.sendChangeTo(changeAddr)
.build()
// alice signing bob's box. Does not work here but works in other cases.
val prover = ctx.newProverBuilder().build()
val signed = prover.sign(unsigned)

// check the signed transaction contains all the context variables
// we attached to the input box
val extensions = signed.getSignedInputs.get(0).getContextVars
contextVars.foreach { cv =>
extensions.containsKey(cv.getId) shouldBe true
extensions.get(cv.getId) shouldBe cv.getValue
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.ergoplatform.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeSta

import scala.util.Try
import sigmastate.eval.CompiletimeIRContext
import sigmastate.interpreter.{ContextExtension, ProverInterpreter}
import sigmastate.interpreter.ProverInterpreter

import scala.collection.mutable

Expand Down Expand Up @@ -57,7 +57,7 @@ class AppkitProvingInterpreter(
* @note requires `unsignedTx` and `boxesToSpend` have the same boxIds in the same order.
*/
def sign(unsignedTx: UnsignedErgoLikeTransaction,
boxesToSpend: IndexedSeq[ErgoBox],
boxesToSpend: IndexedSeq[ExtendedInputBox],
dataBoxes: IndexedSeq[ErgoBox],
stateContext: ErgoLikeStateContext): Try[ErgoLikeTransaction] = Try {
if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend")
Expand All @@ -70,13 +70,13 @@ class AppkitProvingInterpreter(
val outputsCost = unsignedTx.outputCandidates.size * params.outputCost
val initialCost: Long = inputsCost + dataInputsCost + outputsCost

val transactionContext = TransactionContext(boxesToSpend.map(_.box), dataBoxes, unsignedTx)

val provedInputs = mutable.ArrayBuilder.make[Input]()
var currentCost = initialCost
for ((inputBox, boxIdx) <- boxesToSpend.zipWithIndex) {
val unsignedInput = unsignedTx.inputs(boxIdx)
require(util.Arrays.equals(unsignedInput.boxId, inputBox.id))

val transactionContext = TransactionContext(boxesToSpend, dataBoxes, unsignedTx)
require(util.Arrays.equals(unsignedInput.boxId, inputBox.box.id))

val context = new ErgoLikeContext(
ErgoInterpreter.avlTreeFromDigest(stateContext.previousStateDigest),
Expand All @@ -86,14 +86,14 @@ class AppkitProvingInterpreter(
transactionContext.boxesToSpend,
transactionContext.spendingTransaction,
boxIdx.toShort,
ContextExtension.empty,
inputBox.extension,
ValidationRules.currentSettings,
params.maxBlockCost,
currentCost,
(params.blockVersion - 1).toByte
)

prove(inputBox.ergoTree, context, unsignedTx.messageToSign).mapOrThrow { proverResult =>
prove(inputBox.box.ergoTree, context, unsignedTx.messageToSign).mapOrThrow { proverResult =>
currentCost += proverResult.cost
if (currentCost > context.costLimit)
throw new Exception(s"Cost of transaction $unsignedTx exceeds limit ${context.costLimit}")
Expand Down
107 changes: 107 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/ContextVar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.ergoplatform.appkit;

import org.bouncycastle.math.ec.ECPoint;
import sigmastate.AvlTreeData;
import sigmastate.Values;
import special.sigma.GroupElement;

import java.math.BigInteger;
import java.util.Objects;

/**
* Represents one context variable binding (id -> value), where
* id is in range [0 .. Byte.MaxValue].
* Can be attached to each input box of the unsigned transaction.
* @see sigmastate.interpreter.ContextExtension
*/
public class ContextVar {
/** Minimal valid id of a context variable. */
public static final byte MIN_ID = 0;

/** Maximal valid id of a context variable. */
public static final byte MAX_ID = Byte.MAX_VALUE;

private final byte _id;
private final ErgoValue<?> _value;

/**
* Construct a new instance
*
* @param id identifier of the variable in range [0 .. Byte.MaxValue].
* @param value value of the variable
* @see sigmastate.interpreter.ContextExtension
* @see ErgoValue
*/
public ContextVar(byte id, ErgoValue<?> value) {
if (id < MIN_ID)
throw new IllegalArgumentException(String.format(
"Context variable id should be in range [0 .. $d]: %d", MAX_ID, id));
_id = id;
_value = value;
}

/** Returns the id of this variable. */
public byte getId() { return _id; }

/** Returns the value of this variable. */
public ErgoValue<?> getValue() { return _value; }

@Override
public int hashCode() {
return _value.hashCode() + _id;
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true; // same instance
if (obj instanceof ContextVar) {
ContextVar other = (ContextVar)obj;
return _id == other._id && Objects.equals(_value, other._value);
}
return false;
}

static public ContextVar of(byte id, byte value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, short value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, int value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, long value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, BigInteger value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, ECPoint value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, GroupElement value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, Values.SigmaBoolean value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, AvlTreeData value) {
return new ContextVar(id, ErgoValue.of(value));
}

static public ContextVar of(byte id, byte[] arr) {
return new ContextVar(id, ErgoValue.of(arr));
}

static public <T> ContextVar of(byte id, ErgoValue<T> v) {
return new ContextVar(id, v);
}
}
Loading

0 comments on commit 8c48ffd

Please sign in to comment.