Skip to content

Commit

Permalink
feat: Add AssetLockPayload and AssetUnlockPayload
Browse files Browse the repository at this point in the history
Signed-off-by: HashEngineering <[email protected]>
  • Loading branch information
HashEngineering committed Sep 8, 2023
1 parent f098b0b commit 248a68c
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 0 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/org/bitcoinj/core/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public enum Type {
TRANSACTION_PROVIDER_UPDATE_REVOKE(4),
TRANSACTION_COINBASE(5),
TRANSACTION_QUORUM_COMMITMENT(6),
TRANSACTION_ASSET_LOCK(8),
TRANSACTION_ASSET_UNLOCK(9),
TRANSACTION_TYPE_MAX(10),
TRANSACTION_UNKNOWN(1024);

final int value;
Expand Down Expand Up @@ -1704,7 +1707,15 @@ protected void setExtraPayloadObject() {
case TRANSACTION_QUORUM_COMMITMENT:
extraPayloadObject = new FinalCommitmentTxPayload(params, this);
break;
case TRANSACTION_ASSET_LOCK:
extraPayloadObject = new AssetLockPayload(params, this);
break;
case TRANSACTION_ASSET_UNLOCK:
extraPayloadObject = new AssetUnlockPayload(params, this);
break;
}
if (extraPayloadObject != null)
extraPayloadObject.setParent(this);
}

/* returns false if inputs > 4 or there are less than the required confirmations */
Expand All @@ -1723,6 +1734,7 @@ public boolean isSimple() {
public boolean requiresInputs() {
switch (getType()) {
case TRANSACTION_QUORUM_COMMITMENT:
case TRANSACTION_ASSET_UNLOCK:
return false;
default:
return true;
Expand Down
102 changes: 102 additions & 0 deletions core/src/main/java/org/bitcoinj/evolution/AssetLockPayload.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2023 Dash Core Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.bitcoinj.evolution;


import com.google.common.collect.Lists;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.ProtocolException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.VarInt;
import org.json.JSONObject;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import static org.bitcoinj.core.Transaction.Type.TRANSACTION_ASSET_LOCK;

public class AssetLockPayload extends SpecialTxPayload {

public static final int CURRENT_VERSION = 1;
public static final Transaction.Type SPECIALTX_TYPE = TRANSACTION_ASSET_LOCK;
private ArrayList<TransactionOutput> creditOutputs;

public AssetLockPayload(NetworkParameters params, Transaction tx) {
super(params, tx);
}

public AssetLockPayload(NetworkParameters params, List<TransactionOutput> creditOutputs) {
this(params, CURRENT_VERSION, creditOutputs);
}

public AssetLockPayload(NetworkParameters params, int version, List<TransactionOutput> creditOutputs) {
super(params, version);
this.creditOutputs = new ArrayList<>(creditOutputs);
length = new VarInt(creditOutputs.size()).getSizeInBytes();
creditOutputs.forEach(output -> length += output.getMessageSize());
}
@Override
protected void parse() throws ProtocolException {
super.parse();
int size = (int) readVarInt();
creditOutputs = Lists.newArrayList();
for (int i = 0; i < size; ++i) {
TransactionOutput output = new TransactionOutput(params, null, payload, cursor);
cursor += output.getMessageSize();
creditOutputs.add(output);
}
length = cursor - offset;
}

@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
super.bitcoinSerializeToStream(stream);
stream.write(new VarInt(creditOutputs.size()).encode());
for (int i = 0; i < creditOutputs.size(); ++i) {
creditOutputs.get(i).bitcoinSerialize(stream);
}
}

public int getCurrentVersion() {
return CURRENT_VERSION;
}

public String toString() {
return String.format("AssetLockPayload(creditOutputs: %d)",
creditOutputs.size());
}

@Override
public Transaction.Type getType() {
return Transaction.Type.TRANSACTION_ASSET_UNLOCK;
}

@Override
public String getName() {
return "AssetLock";
}

@Override
public JSONObject toJson() {
JSONObject result = super.toJson();
creditOutputs.forEach(output -> result.append("creditOutputs", output.toString()));
return result;
}
}
110 changes: 110 additions & 0 deletions core/src/main/java/org/bitcoinj/evolution/AssetUnlockPayload.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2023 Dash Core Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.bitcoinj.evolution;


import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.ProtocolException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.BLSLazySignature;
import org.json.JSONObject;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;

import static org.bitcoinj.core.Transaction.Type.TRANSACTION_ASSET_UNLOCK;

public class AssetUnlockPayload extends SpecialTxPayload {

public static final int CURRENT_VERSION = 1;
public static final Transaction.Type SPECIALTX_TYPE = TRANSACTION_ASSET_UNLOCK;

private long index;
private long fee;
private long requestedHeight;
private Sha256Hash quorumHash;
BLSLazySignature quorumSig;
public AssetUnlockPayload(NetworkParameters params, Transaction tx) {
super(params, tx);
}


public AssetUnlockPayload(NetworkParameters params, int version, long index, long fee, int requestedHeight, Sha256Hash quorumHash, BLSLazySignature quorumSig) {
super(params, version);
this.index = index;
this.fee = fee;
this.requestedHeight = requestedHeight;
this.quorumHash = quorumHash;
this.quorumSig = quorumSig;
length = 8 + 4 + 4 + 32 + quorumSig.getMessageSize();
}

@Override
protected void parse() throws ProtocolException {
super.parse();
index = readInt64();
fee = readUint32();
requestedHeight = readUint32();
quorumHash = readHash();
quorumSig = new BLSLazySignature(params, payload, cursor);
cursor += quorumSig.getMessageSize();
length = cursor - offset;
}

@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
super.bitcoinSerializeToStream(stream);
Utils.uint64ToByteStreamLE(BigInteger.valueOf(index), stream);
Utils.uint32ToByteStreamLE(fee, stream);
Utils.uint32ToByteStreamLE(requestedHeight, stream);
stream.write(quorumHash.getReversedBytes());
quorumSig.bitcoinSerialize(stream);
}

public int getCurrentVersion() {
return CURRENT_VERSION;
}

public String toString() {
return String.format("AssetUnlockPayload(index: %d, fee: %d, requestedHeight: %d, quorumHash: %s, quorumSig: %s)",
index, fee, requestedHeight, quorumHash, quorumSig);
}

@Override
public Transaction.Type getType() {
return TRANSACTION_ASSET_UNLOCK;
}

@Override
public String getName() {
return "AssetUnlock";
}

@Override
public JSONObject toJson() {
JSONObject result = super.toJson();
result.put("index", index);
result.put("fee", fee);
result.put("requestedHeight", requestedHeight);
result.put("quorumHash", quorumHash);
result.put("quorumSig", quorumSig);
return result;
}
}
118 changes: 118 additions & 0 deletions core/src/test/java/org/bitcoinj/evolution/AssetLockTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.bitcoinj.evolution;

import com.google.common.collect.Lists;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.crypto.BLSLazySignature;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.wallet.DefaultRiskAnalysis;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;

import static org.bitcoinj.core.Coin.CENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

public class AssetLockTest {
UnitTestParams PARAMS = UnitTestParams.get();
ArrayList<Transaction> dummyTransactions;
ArrayList<TransactionOutput> dummyOutputs;

@Before
public void setupDummyInputs()
{
dummyTransactions = Lists.newArrayListWithCapacity(2);
dummyTransactions.add(new Transaction(PARAMS));
dummyTransactions.add(new Transaction(PARAMS));
dummyOutputs = Lists.newArrayListWithCapacity(4);
// Add some keys to the keystore:
ECKey [] key = new ECKey[] {
new ECKey(), new ECKey(), new ECKey(), new ECKey()
};

dummyTransactions.get(0).addOutput(CENT.multiply(11), ScriptBuilder.createP2PKOutputScript(key[0]));
dummyTransactions.get(0).addOutput(CENT.multiply(50), ScriptBuilder.createP2PKOutputScript(key[1]));
dummyOutputs.addAll(dummyTransactions.get(0).getOutputs());


dummyTransactions.get(0).addOutput(CENT.multiply(21), ScriptBuilder.createP2PKOutputScript(key[2]));
dummyTransactions.get(0).addOutput(CENT.multiply(22), ScriptBuilder.createP2PKOutputScript(key[3]));
dummyOutputs.addAll(dummyTransactions.get(1).getOutputs());
}

private Transaction createAssetLockTx(ECKey key) {

ArrayList<TransactionOutput> creditOutputs = Lists.newArrayListWithCapacity(2);
creditOutputs.add(new TransactionOutput(PARAMS, null, CENT.multiply(17), ScriptBuilder.createP2PKOutputScript(key).getProgram()));
creditOutputs.add(new TransactionOutput(PARAMS, null, CENT.multiply(13), ScriptBuilder.createP2PKOutputScript(key).getProgram()));

AssetLockPayload assetLockTx = new AssetLockPayload(PARAMS, 1, creditOutputs);

Transaction tx = new Transaction(PARAMS);
tx.setVersionAndType(3, Transaction.Type.TRANSACTION_ASSET_LOCK);
tx.setExtraPayload(assetLockTx);
tx.addInput(dummyTransactions.get(0).getTxId(), 1, new Script(new byte[65]));
tx.addOutput(CENT.multiply(30), ScriptBuilder.createOpReturnScript(new byte[0]));
tx.addOutput(CENT.multiply(20), ScriptBuilder.createP2PKOutputScript(key));
return tx;
}

private Transaction createAssetUnlockTx(ECKey key)
{
int version = 1;
// just a big number bigger than uint32_t
long index = 0x001122334455667788L;
// big enough to overflow int32_t
int fee = 2000000000;
// just big enough to overflow uint16_t
int requestedHeight = 1000000;
Sha256Hash quorumHash = Sha256Hash.ZERO_HASH;
BLSLazySignature quorumSig = new BLSLazySignature(PARAMS);
AssetUnlockPayload assetUnlockTx = new AssetUnlockPayload(PARAMS, version, index, fee, requestedHeight, quorumHash, quorumSig);

Transaction tx = new Transaction(PARAMS);
tx.setVersionAndType(3, Transaction.Type.TRANSACTION_ASSET_UNLOCK);
tx.setExtraPayload(assetUnlockTx);

tx.addOutput(CENT.multiply(10), ScriptBuilder.createP2PKOutputScript(key));
tx.addOutput(CENT.multiply(20), ScriptBuilder.createP2PKOutputScript(key));

return tx;
}

@Test
public void assetLock() {
ECKey key = new ECKey();

Transaction tx = createAssetLockTx(key);
assertSame(DefaultRiskAnalysis.RuleViolation.NONE, DefaultRiskAnalysis.isStandard(tx));

tx.verify();
assertTrue(tx.getInputs().stream().allMatch(input -> DefaultRiskAnalysis.isInputStandard(input) == DefaultRiskAnalysis.RuleViolation.NONE));

// Check version
assertEquals(3, tx.getVersionShort());
AssetLockPayload lockPayload = (AssetLockPayload) tx.getExtraPayloadObject();
assertEquals(1, lockPayload.getVersion());
}

@Test
public void assetUnlock() {
ECKey key = new ECKey();
final Transaction tx = createAssetUnlockTx(key);
assertSame(DefaultRiskAnalysis.RuleViolation.NONE, DefaultRiskAnalysis.isStandard(tx));
tx.verify();

// Check version
assertEquals(3, tx.getVersionShort());
AssetLockPayload lockPayload = (AssetLockPayload) tx.getExtraPayloadObject();
assertEquals(1, lockPayload.getVersion());
}
}

0 comments on commit 248a68c

Please sign in to comment.