Fixed old Qora v1 "GenesisAccount" by replacing with NullAccount

NullAccount has 'empty' public key (32 bytes of zeros) compared
with GenesisAccount's vague sometimes 8 bytes, sometimes 32 bytes
public key.

NullAccount has static public key and address, plus overridden
methods to speed up pointless calls like verify().

Genesis Block also tidied up, dropping old Qora v1 compatibility
and using proper block signature and public key to generate
minter's block signature.

Genesis Block transaction processing also simplified, with no need
to access repository to handle fake references, due to new
last-reference code (which will need to be merged).

Dropped support for old, broken RMD160 code.
This commit is contained in:
catbref 2020-04-27 14:57:27 +01:00
parent 2602bb01e1
commit 9e2663b11b
14 changed files with 80 additions and 106 deletions

View File

@ -3,6 +3,9 @@ package org.qortal.account;
import java.math.BigDecimal;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.block.BlockChain;
@ -15,6 +18,7 @@ import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction;
import org.qortal.utils.Base58;
@XmlAccessorType(XmlAccessType.NONE) // Stops JAX-RS errors when unmarshalling blockchain config
public class Account {
private static final Logger LOGGER = LogManager.getLogger(Account.class);

View File

@ -1,13 +0,0 @@
package org.qortal.account;
import org.qortal.repository.Repository;
public final class GenesisAccount extends PublicKeyAccount {
public static final byte[] PUBLIC_KEY = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
public GenesisAccount(Repository repository) {
super(repository, PUBLIC_KEY);
}
}

View File

@ -0,0 +1,24 @@
package org.qortal.account;
import org.qortal.crypto.Crypto;
import org.qortal.repository.Repository;
public final class NullAccount extends PublicKeyAccount {
public static final byte[] PUBLIC_KEY = new byte[32];
public static final String ADDRESS = Crypto.toAddress(PUBLIC_KEY);
public NullAccount(Repository repository) {
super(repository, PUBLIC_KEY, ADDRESS);
}
protected NullAccount() {
}
@Override
public boolean verify(byte[] signature, byte[] message) {
// Can't sign, hence can't verify.
return false;
}
}

View File

@ -22,6 +22,18 @@ public class PublicKeyAccount extends Account {
this.publicKey = edPublicKeyParams.getEncoded();
}
protected PublicKeyAccount(Repository repository, byte[] publicKey, String address) {
super(repository, address);
this.publicKey = publicKey;
this.edPublicKeyParams = null;
}
protected PublicKeyAccount() {
this.publicKey = null;
this.edPublicKeyParams = null;
}
public byte[] getPublicKey() {
return this.publicKey;
}

View File

@ -14,7 +14,7 @@ import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.ciyam.at.Timestamp;
import org.qortal.account.Account;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
@ -270,7 +270,7 @@ public class QortalATAPI extends API {
byte[] reference = this.getLastReference();
BigDecimal amount = BigDecimal.valueOf(unscaledAmount, 8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
recipient.getAddress(), amount, this.atData.getAssetId(), new byte[0]);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
@ -289,7 +289,7 @@ public class QortalATAPI extends API {
long timestamp = this.getNextTransactionTimestamp();
byte[] reference = this.getLastReference();
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
recipient.getAddress(), BigDecimal.ZERO, this.atData.getAssetId(), message);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
@ -316,7 +316,7 @@ public class QortalATAPI extends API {
byte[] reference = this.getLastReference();
BigDecimal amount = BigDecimal.valueOf(finalBalance, 8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
creator.getAddress(), amount, this.atData.getAssetId(), new byte[0]);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);

View File

@ -7,27 +7,21 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.account.GenesisAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.account.NullAccount;
import org.qortal.crypto.Crypto;
import org.qortal.data.asset.AssetData;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.IssueAssetTransactionData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.group.Group;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.ApprovalStatus;
import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.transform.TransformationException;
import org.qortal.transform.transaction.TransactionTransformer;
@ -39,9 +33,8 @@ public class GenesisBlock extends Block {
private static final Logger LOGGER = LogManager.getLogger(GenesisBlock.class);
private static final byte[] GENESIS_REFERENCE = new byte[] {
1, 1, 1, 1, 1, 1, 1, 1
}; // NOTE: Neither 64 nor 128 bytes!
private static final byte[] GENESIS_BLOCK_REFERENCE = new byte[128];
private static final byte[] GENESIS_TRANSACTION_REFERENCE = new byte[64];
@XmlAccessorType(XmlAccessType.FIELD)
public static class GenesisInfo {
@ -96,37 +89,16 @@ public class GenesisBlock extends Block {
transactionData.setFee(BigDecimal.ZERO.setScale(8));
if (transactionData.getCreatorPublicKey() == null)
transactionData.setCreatorPublicKey(GenesisAccount.PUBLIC_KEY);
transactionData.setCreatorPublicKey(NullAccount.PUBLIC_KEY);
if (transactionData.getTimestamp() == 0)
transactionData.setTimestamp(info.timestamp);
});
// For version 1, extract any ISSUE_ASSET transactions into initialAssets and only allow GENESIS transactions
if (info.version == 1) {
List<TransactionData> issueAssetTransactions = transactionsData.stream()
.filter(transactionData -> transactionData.getType() == TransactionType.ISSUE_ASSET).collect(Collectors.toList());
transactionsData.removeAll(issueAssetTransactions);
// There should be only GENESIS transactions left;
if (transactionsData.stream().anyMatch(transactionData -> transactionData.getType() != TransactionType.GENESIS)) {
LOGGER.error("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)");
throw new RuntimeException("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)");
}
// Convert ISSUE_ASSET transactions into initial assets
initialAssets = issueAssetTransactions.stream().map(transactionData -> {
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(),
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), "", false, Group.NO_GROUP, issueAssetTransactionData.getReference());
}).collect(Collectors.toList());
}
byte[] reference = GENESIS_REFERENCE;
byte[] reference = GENESIS_BLOCK_REFERENCE;
int transactionCount = transactionsData.size();
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
byte[] minterPublicKey = GenesisAccount.PUBLIC_KEY;
byte[] minterPublicKey = NullAccount.PUBLIC_KEY;
byte[] bytesForSignature = getBytesForMinterSignature(info.timestamp, reference, minterPublicKey);
byte[] minterSignature = calcGenesisMinterSignature(bytesForSignature);
byte[] transactionsSignature = calcGenesisTransactionsSignature();
@ -212,24 +184,16 @@ public class GenesisBlock extends Block {
try {
// Passing expected size to ByteArrayOutputStream avoids reallocation when adding more bytes than default 32.
// See below for explanation of some of the values used to calculated expected size.
ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + 8 + 32);
ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 128 + 32);
/*
* NOTE: Historic code had genesis block using Longs.toByteArray(version) compared to standard block's Ints.toByteArray. The subsequent
* Bytes.ensureCapacity(versionBytes, 0, 4) did not truncate versionBytes back to 4 bytes either. This means 8 bytes were used even though
* VERSION_LENGTH is set to 4. Correcting this historic bug will break genesis block signatures!
*/
// For Qortal, we use genesis timestamp instead
// Genesis block timestamp
bytes.write(Longs.toByteArray(timestamp));
/*
* NOTE: Historic code had the reference expanded to only 64 bytes whereas standard block references are 128 bytes. Correcting this historic bug
* will break genesis block signatures!
*/
bytes.write(Bytes.ensureCapacity(reference, 64, 0));
// Block's reference
bytes.write(reference);
// NOTE: Genesis account's public key is only 8 bytes, not the usual 32, so we have to pad.
bytes.write(Bytes.ensureCapacity(minterPublicKey, 32, 0));
// Minting account's public key (typically NullAccount)
bytes.write(minterPublicKey);
return bytes.toByteArray();
} catch (IOException e) {
@ -295,26 +259,18 @@ public class GenesisBlock extends Block {
public void process() throws DataException {
LOGGER.info(String.format("Using genesis block timestamp of %d", this.blockData.getTimestamp()));
// If we're a version 1 genesis block, create assets now
if (this.blockData.getVersion() == 1)
for (AssetData assetData : initialAssets)
repository.getAssetRepository().save(assetData);
/*
* Some transactions will be missing references and signatures,
* so we generate them by trial-processing transactions and using
* account's last-reference to fill in the gaps for reference,
* so we generate them by using <tt>GENESIS_TRANSACTION_REFERENCE</tt>
* and a duplicated SHA256 digest for signature
*/
this.repository.setSavepoint();
try {
for (Transaction transaction : this.getTransactions()) {
TransactionData transactionData = transaction.getTransactionData();
Account creator = new PublicKeyAccount(this.repository, transactionData.getCreatorPublicKey());
// Missing reference?
if (transactionData.getReference() == null)
transactionData.setReference(creator.getLastReference());
transactionData.setReference(GENESIS_TRANSACTION_REFERENCE);
// Missing signature?
if (transactionData.getSignature() == null) {
@ -324,18 +280,11 @@ public class GenesisBlock extends Block {
transactionData.setSignature(signature);
}
// Missing approval status (not used in V1)
// Approval status
transactionData.setApprovalStatus(ApprovalStatus.NOT_REQUIRED);
// Ask transaction to update references, etc.
transaction.processReferencesAndFees();
creator.setLastReference(transactionData.getSignature());
}
} catch (TransformationException e) {
throw new RuntimeException("Can't process genesis block transaction", e);
} finally {
this.repository.rollbackToSavepoint();
}
// Save transactions into repository ready for processing

View File

@ -6,7 +6,7 @@ import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.transaction.Transaction.TransactionType;
import io.swagger.v3.oas.annotations.media.Schema;
@ -31,14 +31,14 @@ public class ATTransactionData extends TransactionData {
}
public void afterUnmarshal(Unmarshaller u, Object parent) {
this.creatorPublicKey = GenesisAccount.PUBLIC_KEY;
this.creatorPublicKey = NullAccount.PUBLIC_KEY;
}
/** From repository */
public ATTransactionData(BaseTransactionData baseTransactionData, String atAddress, String recipient, BigDecimal amount, Long assetId, byte[] message) {
super(TransactionType.AT, baseTransactionData);
this.creatorPublicKey = GenesisAccount.PUBLIC_KEY;
this.creatorPublicKey = NullAccount.PUBLIC_KEY;
this.atAddress = atAddress;
this.recipient = recipient;
this.amount = amount;

View File

@ -6,7 +6,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.block.GenesisBlock;
import org.qortal.transaction.Transaction.TransactionType;
@ -36,10 +36,10 @@ public class AccountFlagsTransactionData extends TransactionData {
/*
* If we're being constructed as part of the genesis block info inside blockchain config
* and no specific creator's public key is supplied
* then use genesis account's public key.
* then use null account's public key.
*/
if (parent instanceof GenesisBlock.GenesisInfo && this.creatorPublicKey == null)
this.creatorPublicKey = GenesisAccount.PUBLIC_KEY;
this.creatorPublicKey = NullAccount.PUBLIC_KEY;
}
/** From repository */

View File

@ -6,7 +6,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.block.GenesisBlock;
import org.qortal.transaction.Transaction.TransactionType;
@ -33,10 +33,10 @@ public class AccountLevelTransactionData extends TransactionData {
/*
* If we're being constructed as part of the genesis block info inside blockchain config
* and no specific creator's public key is supplied
* then use genesis account's public key.
* then use null account's public key.
*/
if (parent instanceof GenesisBlock.GenesisInfo && this.creatorPublicKey == null)
this.creatorPublicKey = GenesisAccount.PUBLIC_KEY;
this.creatorPublicKey = NullAccount.PUBLIC_KEY;
}
/** From repository, network/API */

View File

@ -34,10 +34,8 @@ public class GenesisTransactionData extends TransactionData {
super(TransactionType.GENESIS);
}
/** From repository (V2) */
/** From repository */
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount, long assetId) {
// No groupID, null reference, zero fee, no approval required, height always 1
// super(TransactionType.GENESIS, timestamp, Group.NO_GROUP, null, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, ApprovalStatus.NOT_REQUIRED, 1, signature);
super(TransactionType.GENESIS, baseTransactionData);
this.recipient = recipient;
@ -45,7 +43,7 @@ public class GenesisTransactionData extends TransactionData {
this.assetId = assetId;
}
/** From repository (V1, where asset locked to QORT) */
/** From repository (where asset locked to QORT) */
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount) {
this(baseTransactionData, recipient, amount, Asset.QORT);
}

View File

@ -5,7 +5,7 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.block.GenesisBlock;
import org.qortal.transaction.Transaction.TransactionType;
@ -51,10 +51,10 @@ public class IssueAssetTransactionData extends TransactionData {
/*
* If we're being constructed as part of the genesis block info inside blockchain config
* and no specific issuer's public key is supplied
* then use genesis account's public key.
* then use null account's public key.
*/
if (parent instanceof GenesisBlock.GenesisInfo && this.issuerPublicKey == null)
this.issuerPublicKey = GenesisAccount.PUBLIC_KEY;
this.issuerPublicKey = NullAccount.PUBLIC_KEY;
this.creatorPublicKey = this.issuerPublicKey;
}

View File

@ -5,7 +5,7 @@ import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.asset.Asset;
import org.qortal.data.transaction.AccountFlagsTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -68,8 +68,8 @@ public class AccountFlagsTransaction extends Transaction {
public ValidationResult isValid() throws DataException {
Account creator = getCreator();
// Only genesis account can modify flags
if (!creator.getAddress().equals(new GenesisAccount(repository).getAddress()))
// Only null account can modify flags
if (!creator.getAddress().equals(NullAccount.ADDRESS))
return ValidationResult.NO_FLAG_PERMISSION;
// Check fee is zero or positive

View File

@ -5,7 +5,7 @@ import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.data.transaction.AccountLevelTransactionData;
@ -70,7 +70,7 @@ public class AccountLevelTransaction extends Transaction {
Account creator = getCreator();
// Only genesis account can modify level
if (!creator.getAddress().equals(new GenesisAccount(repository).getAddress()))
if (!creator.getAddress().equals(new NullAccount(repository).getAddress()))
return ValidationResult.NO_FLAG_PERMISSION;
// Check fee is zero or positive

View File

@ -5,7 +5,7 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import org.qortal.account.GenesisAccount;
import org.qortal.account.NullAccount;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.GenesisTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -49,7 +49,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
long assetId = byteBuffer.getLong();
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, null, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, null, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
return new GenesisTransactionData(baseTransactionData, recipient, amount, assetId);
}