Skip to content
Open
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
89 changes: 74 additions & 15 deletions framework/src/main/java/org/tron/core/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ public class Wallet {
"BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21])"));
private static final byte[] SHIELDED_TRC20_LOG_TOPICS_BURN_TOKEN = Hash.sha3(ByteArray
.fromString("TokenBurn(address,uint256,bytes32[3])"));
private static final byte[] SHIELDED_TRC20_LOG_TOPICS_NOTE_SPENT = Hash.sha3(ByteArray
.fromString("NoteSpent(bytes32)"));
private static final String BROADCAST_TRANS_FAILED = "Broadcast transaction {} failed, {}.";

@Getter
Expand Down Expand Up @@ -3672,9 +3674,7 @@ public ShieldedTRC20Parameters createShieldedContractParameters(
builder.setTransparentToAddress(transparentToAddressTvm);
builder.setTransparentToAmount(toAmount);

Optional<byte[]> cipher = NoteEncryption.Encryption
.encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress);
cipher.ifPresent(builder::setBurnCiphertext);
builder.setOvk(ovk);

ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk);
GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0);
Expand Down Expand Up @@ -3799,9 +3799,7 @@ public ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk(
System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20);
builder.setTransparentToAddress(transparentToAddressTvm);
builder.setTransparentToAmount(toAmount);
Optional<byte[]> cipher = NoteEncryption.Encryption
.encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress);
cipher.ifPresent(builder::setBurnCiphertext);
builder.setOvk(ovk);
GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0);
buildShieldedTRC20InputWithAK(builder, spendNote, ak, nsk);
if (receiveSize == 1) {
Expand Down Expand Up @@ -3838,6 +3836,8 @@ private int getShieldedTRC20LogType(TransactionInfo.Log log, byte[] contractAddr
return 3;
} else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_BURN_TOKEN)) {
return 4;
} else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_NOTE_SPENT)) {
return 5;
}
}
return 0;
Expand Down Expand Up @@ -3909,7 +3909,9 @@ private DecryptNotesTRC20 queryTRC20NoteByIvk(long startNum, long endNum,
int index = 0;
for (TransactionInfo.Log log : logList) {
int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress);
if (logType > 0) {
// Only note-producing log types (1..3) advance the note index;
// TokenBurn (4) and NoteSpent (5) do not emit a leaf.
if (logType > 0 && logType < 4) {
noteBuilder = DecryptNotesTRC20.NoteTx.newBuilder();
noteBuilder.setTxid(ByteString.copyFrom(txId));
noteBuilder.setIndex(index);
Expand Down Expand Up @@ -4001,7 +4003,8 @@ public DecryptNotesTRC20 scanShieldedTRC20NotesByIvk(

private Optional<DecryptNotesTRC20.NoteTx> getNoteTxFromLogListByOvk(
DecryptNotesTRC20.NoteTx.Builder builder,
TransactionInfo.Log log, byte[] ovk, int logType) throws ZksnarkException {
TransactionInfo.Log log, byte[] ovk, int logType, byte[] pendingNf)
throws ZksnarkException {
byte[] logData = log.getData().toByteArray();
if (!ArrayUtils.isEmpty(logData)) {
if (logType > 0 && logType < 4) {
Expand Down Expand Up @@ -4040,18 +4043,32 @@ private Optional<DecryptNotesTRC20.NoteTx> getNoteTxFromLogListByOvk(
}
}
} else if (logType == 4) {
//Data = toAddress(32) + value(32) + ciphertext(80) + padding(16)
// Data = toAddress(32) + value(32) + cipher(80) + nonce(12) + reserved/version(4)
if (logData.length < 64 + NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE) {
return Optional.empty();
}
byte[] logToAddress = ByteArray.subArray(logData, 12, 32);
byte[] logAmountArray = ByteArray.subArray(logData, 32, 64);
byte[] cipher = ByteArray.subArray(logData, 64, 144);
byte[] nonceFromLog = ByteArray.subArray(logData, 144,
144 + NoteEncryption.Encryption.BURN_NONCE_LEN);
byte[] reservedFromLog = ByteArray.subArray(logData,
144 + NoteEncryption.Encryption.BURN_NONCE_LEN,
144 + NoteEncryption.Encryption.BURN_NONCE_LEN
+ NoteEncryption.Encryption.BURN_RESERVED_LEN);
BigInteger logAmount = ByteUtil.bytesToBigInteger(logAmountArray);
byte[] plaintext;
byte[] amountArray = new byte[32];
byte[] decryptedAddress = new byte[20];

Optional<byte[]> decryptedText = NoteEncryption.Encryption
.decryptBurnMessageByOvk(ovk, cipher);
.decryptBurnMessageByOvk(ovk, cipher, nonceFromLog, reservedFromLog, pendingNf);

if (decryptedText.isPresent()) {
plaintext = decryptedText.get();
if (plaintext[32] != Wallet.getAddressPreFixByte()) {
return Optional.empty();
}
System.arraycopy(plaintext, 0, amountArray, 0, 32);
System.arraycopy(plaintext, 33, decryptedAddress, 0, 20);
BigInteger decryptedAmount = ByteUtil.bytesToBigInteger(amountArray);
Expand Down Expand Up @@ -4091,15 +4108,24 @@ public DecryptNotesTRC20 scanShieldedTRC20NotesByOvk(long startNum, long endNum,
if (!Objects.isNull(logList)) {
Optional<DecryptNotesTRC20.NoteTx> noteTx;
int index = 0;
byte[] pendingNf = null;
for (TransactionInfo.Log log : logList) {
int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress);
if (logType > 0) {
if (logType == 5) {
byte[] logData = log.getData().toByteArray();
if (logData.length >= 32) {
pendingNf = ByteArray.subArray(logData, 0, 32);
}
} else if (logType > 0) {
noteBuilder = DecryptNotesTRC20.NoteTx.newBuilder();
noteBuilder.setTxid(ByteString.copyFrom(txid));
noteBuilder.setIndex(index);
index += 1;
noteTx = getNoteTxFromLogListByOvk(noteBuilder, log, ovk, logType);
noteTx = getNoteTxFromLogListByOvk(noteBuilder, log, ovk, logType, pendingNf);
noteTx.ifPresent(builder::addNoteTxs);
if (logType == 4) {
pendingNf = null;
}
}
}
}
Expand Down Expand Up @@ -4283,12 +4309,45 @@ public BytesMessage getTriggerInputForShieldedTRC20Contract(
parameterType);
if (parametersBuilder.getShieldedTRC20ParametersType() == ShieldedTRC20ParametersType.BURN) {
byte[] burnCiper = ByteArray.fromHexString(shieldedTRC20Parameters.getTriggerContractInput());
if (!ArrayUtils.isEmpty(burnCiper) && burnCiper.length == 80) {
parametersBuilder.setBurnCiphertext(burnCiper);
} else {
if (ArrayUtils.isEmpty(burnCiper)
|| burnCiper.length != NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE) {
if (!ArrayUtils.isEmpty(burnCiper) && burnCiper.length == 80) {
throw new ZksnarkException(
"legacy 80-byte burn cipher is deprecated and rejected; expected "
+ NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE + "-byte burn record");
}
throw new ZksnarkException(
"invalid shielded TRC-20 contract parameters for burn trigger input");
}
// v2-only: length alone would accept a legacy all-zero suffix and bypass
// the nf-bound nonce. Require reserved==v2 marker and nonce==derive(nf).
byte[] reserved = Arrays.copyOfRange(burnCiper,
NoteEncryption.Encryption.BURN_RESERVED_OFFSET,
NoteEncryption.Encryption.BURN_RESERVED_OFFSET
+ NoteEncryption.Encryption.BURN_RESERVED_LEN);
if (!Arrays.equals(reserved, NoteEncryption.Encryption.getBurnRecordV2Marker())) {
throw new ZksnarkException(
"burn trigger input must be v2 (reserved=0x00000001); legacy/unknown markers rejected");
}
if (shieldedTRC20Parameters.getSpendDescriptionList().size() != 1) {
throw new ZksnarkException(
"burn trigger input requires exactly one spendDescription for nf-bound nonce");
}
byte[] nf = shieldedTRC20Parameters.getSpendDescription(0).getNullifier().toByteArray();
if (nf.length != 32) {
throw new ZksnarkException(
"burn trigger input requires 32-byte spendDescription.nullifier");
}
byte[] nonceFromInput = Arrays.copyOfRange(burnCiper,
NoteEncryption.Encryption.BURN_NONCE_OFFSET,
NoteEncryption.Encryption.BURN_NONCE_OFFSET
+ NoteEncryption.Encryption.BURN_NONCE_LEN);
byte[] expectedNonce = NoteEncryption.Encryption.deriveNonceFromNf(nf);
if (!Arrays.equals(nonceFromInput, expectedNonce)) {
throw new ZksnarkException(
"burn trigger input nonce does not match nf-bound nonce");
}
parametersBuilder.setBurnCiphertext(burnCiper);
}
String input = parametersBuilder
.getTriggerContractInput(shieldedTRC20Parameters, spendAuthoritySignature, value, false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.tron.core.zen.address.PaymentAddress;
import org.tron.core.zen.note.Note;
import org.tron.core.zen.note.NoteEncryption;
import org.tron.core.zen.note.NoteEncryption.Encryption;
import org.tron.core.zen.note.OutgoingPlaintext;
import org.tron.protos.contract.ShieldContract;
import org.tron.protos.contract.ShieldContract.ReceiveDescription;
Expand Down Expand Up @@ -61,7 +62,9 @@ public class ShieldedTRC20ParametersBuilder {
@Setter
private BigInteger transparentToAmount;
@Setter
private byte[] burnCiphertext = new byte[80];
private byte[] ovk;
@Setter
private byte[] burnCiphertext = new byte[Encryption.BURN_CIPHER_RECORD_SIZE];

public ShieldedTRC20ParametersBuilder() {

Expand Down Expand Up @@ -207,6 +210,9 @@ private ReceiveDescriptionCapsule generateOutputProof(ReceiveDescriptionInfo out

private void createSpendAuth(byte[] dataToBeSigned) throws ZksnarkException {
for (int i = 0; i < spends.size(); i++) {
if (spends.get(i).expsk == null) {
throw new ZksnarkException("missing expanded spending key for spend authorization");
}
byte[] result = new byte[64];
JLibrustzcash.librustzcashSaplingSpendSig(
new LibrustzcashParam.SpendSigParams(spends.get(i).expsk.getAsk(),
Expand Down Expand Up @@ -292,6 +298,25 @@ public ShieldedTRC20Parameters build(boolean withAsk) throws ZksnarkException {
SpendDescriptionInfo spend = spends.get(0);
spendDescription = generateSpendProof(spend, ctx).getInstance();
builder.addSpendDescription(spendDescription);

if (ovk == null && spend.expsk != null) {
ovk = spend.expsk.getOvk();
}
if (ovk == null) {
throw new ZksnarkException("missing ovk for burn encryption");
}
byte[] nf = spendDescription.getNullifier().toByteArray();
byte[] transparentToAddressTvm = normalizeTransparentToAddress(transparentToAddress);
byte[] addr21 = new byte[21];
addr21[0] = Wallet.getAddressPreFixByte();
System.arraycopy(transparentToAddressTvm, 0, addr21, 1, 20);
Optional<byte[]> cipherOpt = Encryption.encryptBurnMessageByOvk(
ovk, transparentToAmount, addr21, nf);
if (!cipherOpt.isPresent()) {
throw new ZksnarkException("encrypt burn message failed");
}
burnCiphertext = cipherOpt.get();

mergedBytes = ByteUtil.merge(shieldedTRC20Address,
encodeSpendDescriptionWithoutSpendAuthSig(spendDescription));
if (receives.size() == 1) {
Expand All @@ -302,7 +327,7 @@ public ShieldedTRC20Parameters build(boolean withAsk) throws ZksnarkException {
encodeCencCout(receiveDescription));
}
mergedBytes = ByteUtil
.merge(mergedBytes, transparentToAddress, ByteArray.fromLong(valueBalance));
.merge(mergedBytes, transparentToAddressTvm, ByteArray.fromLong(valueBalance));
value = transparentToAmount;
builder.setParameterType("burn");
break;
Expand Down Expand Up @@ -476,12 +501,10 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
throw new IllegalArgumentException("the value must be positive");
}

if (ArrayUtils.isEmpty(transparentToAddress)) {
throw new IllegalArgumentException("the transparent payTo address is null");
}
byte[] transparentToAddressTvm = normalizeTransparentToAddress(transparentToAddress);

payTo[11] = Wallet.getAddressPreFixByte();
System.arraycopy(transparentToAddress, 0, payTo, 12, 20);
System.arraycopy(transparentToAddressTvm, 0, payTo, 12, 20);
ShieldContract.SpendDescription spendDesc = burnParams.getSpendDescription(0);

byte[] spendAuthSign;
Expand All @@ -492,7 +515,6 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
}

byte[] mergedBytes;
byte[] zeros = new byte[16];
mergedBytes = ByteUtil.merge(
spendDesc.getNullifier().toByteArray(),
spendDesc.getAnchor().toByteArray(),
Expand All @@ -503,8 +525,7 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
ByteUtil.bigIntegerToBytes(value, 32),
burnParams.getBindingSignature().toByteArray(),
payTo,
burnCiphertext,
zeros
burnCiphertext
);

byte[] outputOffsetBytes; // 32
Expand All @@ -524,7 +545,7 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
coffsetBytes = ByteUtil.longTo32Bytes(mergedBytes.length + 32 * 3 + 32L * 9);
countBytes = ByteUtil.longTo32Bytes(1L);
ReceiveDescription recvDesc = burnParams.getReceiveDescription(0);
zeros = new byte[12];
byte[] zeros = new byte[12];
mergedBytes = ByteUtil
.merge(mergedBytes,
outputOffsetBytes,
Expand All @@ -542,6 +563,18 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
return Hex.toHexString(mergedBytes);
}

private byte[] normalizeTransparentToAddress(byte[] transparentToAddress) {
if (transparentToAddress != null && transparentToAddress.length == 20) {
return transparentToAddress;
}
if (transparentToAddress != null && transparentToAddress.length == 21) {
byte[] transparentToAddressTvm = new byte[20];
System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20);
return transparentToAddressTvm;
}
throw new IllegalArgumentException("invalid transparentToAddress for burn encryption");
}

public void addSpend(
ExpandedSpendingKey expsk,
Note note,
Expand Down
Loading
Loading