Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 20 additions & 3 deletions framework/src/main/java/org/tron/core/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4035,16 +4035,23 @@ private Optional<DecryptNotesTRC20.NoteTx> getNoteTxFromLogListByOvk(
}
}
} else if (logType == 4) {
//Data = toAddress(32) + value(32) + ciphertext(80) + padding(16)
//Data = toAddress(32) + value(32) + ciphertext(80) + nonce(12) + reserved(4)
// Pre-fix burns wrote 16 bytes of zero padding in place of nonce+reserved; those
// bytes are forwarded as-is and decrypt under the same AEAD path.
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[] nonce = ByteArray.subArray(logData, 144,
144 + NoteEncryption.Encryption.BURN_NONCE_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, nonce);
if (decryptedText.isPresent()) {
plaintext = decryptedText.get();
System.arraycopy(plaintext, 0, amountArray, 0, 32);
Expand Down Expand Up @@ -4278,8 +4285,18 @@ public BytesMessage getTriggerInputForShieldedTRC20Contract(
parameterType);
if (parametersBuilder.getShieldedTRC20ParametersType() == ShieldedTRC20ParametersType.BURN) {
byte[] burnCiper = ByteArray.fromHexString(shieldedTRC20Parameters.getTriggerContractInput());
if (!ArrayUtils.isEmpty(burnCiper) && burnCiper.length == 80) {
if (!ArrayUtils.isEmpty(burnCiper)
&& burnCiper.length == NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE) {
parametersBuilder.setBurnCiphertext(burnCiper);
} else if (!ArrayUtils.isEmpty(burnCiper)
&& burnCiper.length == NoteEncryption.Encryption.BURN_CIPHER_LEN) {
// Legacy 80-byte cipher from clients built before the random-nonce upgrade.
// Pad to the 96-byte record with a zero nonce/reserved tail; on-chain layout
// is identical to the pre-upgrade burn calldata, so this remains backward
// compatible for callers that have not yet rebuilt against the new encoder.
byte[] record = new byte[NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE];
System.arraycopy(burnCiper, 0, record, 0, NoteEncryption.Encryption.BURN_CIPHER_LEN);
parametersBuilder.setBurnCiphertext(record);
} else {
throw new ZksnarkException(
"invalid shielded TRC-20 contract parameters for burn trigger input");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class ShieldedTRC20ParametersBuilder {
@Setter
private BigInteger transparentToAmount;
@Setter
private byte[] burnCiphertext = new byte[80];
private byte[] burnCiphertext = new byte[96];

public ShieldedTRC20ParametersBuilder() {

Expand Down Expand Up @@ -492,7 +492,9 @@ private String burnParamsToHexString(GrpcAPI.ShieldedTRC20Parameters burnParams,
}

byte[] mergedBytes;
byte[] zeros = new byte[16];
// burnCiphertext is a 96-byte record: cipher(80) || nonce(12) || reserved(4),
// occupying the same calldata slot as the previous cipher(80) || zeros(16) layout
// so total length is unchanged.
mergedBytes = ByteUtil.merge(
spendDesc.getNullifier().toByteArray(),
spendDesc.getAnchor().toByteArray(),
Expand All @@ -503,8 +505,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 +525,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 Down
41 changes: 31 additions & 10 deletions framework/src/main/java/org/tron/core/zen/note/NoteEncryption.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.tron.core.zen.note.NoteEncryption.Encryption.NOTEENCRYPTION_CIPHER_KEYSIZE;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -246,15 +247,30 @@ public static Optional<OutPlaintext> attemptOutDecryption(
}

/**
* encrypt the message by ovk used for scanning
* Burn ciphertext record layout: cipher(80) || nonce(12) || reserved(4) = 96 bytes.
* The 12-byte nonce is fresh-per-burn CSPRNG output to satisfy RFC 8439 §4 (key, nonce)
* uniqueness; the trailing 4 bytes are reserved zero, padding the record to fit the
* existing 96-byte slot in burn calldata / TokenBurn event data.
*/
public static final int BURN_CIPHER_RECORD_SIZE = 96;
public static final int BURN_CIPHER_LEN = 80;
public static final int BURN_NONCE_OFFSET = 80;
public static final int BURN_NONCE_LEN = CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES;

private static final SecureRandom BURN_NONCE_RNG = new SecureRandom();

/**
* encrypt the message by ovk used for scanning. Returns a 96-byte record:
* cipher(80) || nonce(12) || reserved zero(4).
*/
public static Optional<byte[]> encryptBurnMessageByOvk(byte[] ovk, BigInteger toAmount,
byte[] transparentToAddress)
throws ZksnarkException {
byte[] plaintext = new byte[64];
byte[] amountArray = ByteUtil.bigIntegerToBytes(toAmount, 32);
byte[] cipherNonce = new byte[12];
byte[] cipher = new byte[80];
byte[] cipherNonce = new byte[BURN_NONCE_LEN];
BURN_NONCE_RNG.nextBytes(cipherNonce);
byte[] cipher = new byte[BURN_CIPHER_LEN];
System.arraycopy(amountArray, 0, plaintext, 0, 32);
System.arraycopy(transparentToAddress, 0, plaintext, 32,
21);
Expand All @@ -265,23 +281,28 @@ public static Optional<byte[]> encryptBurnMessageByOvk(byte[] ovk, BigInteger to
return Optional.empty();
}

return Optional.of(cipher);
byte[] record = new byte[BURN_CIPHER_RECORD_SIZE];
System.arraycopy(cipher, 0, record, 0, BURN_CIPHER_LEN);
System.arraycopy(cipherNonce, 0, record, BURN_NONCE_OFFSET, BURN_NONCE_LEN);
return Optional.of(record);
}

/**
* decrypt the message by ovk used for scanning
* decrypt the message by ovk used for scanning. {@code nonce} is the per-burn nonce
* recovered from the log and is required to be {@link #BURN_NONCE_LEN} bytes. Pre-upgrade
* burn logs carry an all-zero byte field at the nonce offset, which is forwarded as-is
* and decrypts via the same code path.
*/
public static Optional<byte[]> decryptBurnMessageByOvk(byte[] ovk, byte[] ciphertext)
throws ZksnarkException {
public static Optional<byte[]> decryptBurnMessageByOvk(byte[] ovk, byte[] ciphertext,
byte[] nonce) throws ZksnarkException {
byte[] outPlaintext = new byte[64];
byte[] cipherNonce = new byte[12];
if (JLibsodium.cryptoAeadChacha20poly1305IetfDecrypt(new Chacha20poly1305IetfDecryptParams(
outPlaintext, null,
null,
ciphertext, 80,
ciphertext, BURN_CIPHER_LEN,
null,
0,
cipherNonce, ovk)) != 0) {
nonce, ovk)) != 0) {
return Optional.empty();
}
return Optional.of(outPlaintext);
Expand Down
Loading
Loading