Prepare v0.7.0 release build

This commit is contained in:
Vexta Project 2026-09-08 21:39:06 +00:00
commit fba35559f4
4 changed files with 0 additions and 859 deletions

View file

@ -548,76 +548,6 @@ object VextaTransactionSender {
return doubleSha256(preimage) return doubleSha256(preimage)
} }
fun runPqrSignatureHashSelfTest(): Pair<String, String> {
val selected = listOf(
BlockScanner.SpendableUtxo(
txid =
"0000000000000000000000000000000000000000000000000000000000000001",
outputIndex = 3L,
value = 987654L,
height = 1,
addressIndex = 0,
isCoinbase = false,
addressType = BlockScanner.AddressType.MLDSA
)
)
val outputs = listOf(
TransactionOutputData(
value = 123456L,
script = byteArrayOf(0x51)
)
)
val program =
ByteArray(32) { index ->
index.toByte()
}
val v2 = calculatePqrSignatureHash(
selected = selected,
outputs = outputs,
inputIndex = 0,
witnessVersion = 2,
witnessProgram = program,
amount = 987654L,
version = 2,
lockTime = 500L,
inputSequence = 0xfffffffeL
)
val v3 = calculatePqrSignatureHash(
selected = selected,
outputs = outputs,
inputIndex = 0,
witnessVersion = 3,
witnessProgram = program,
amount = 987654L,
version = 2,
lockTime = 500L,
inputSequence = 0xfffffffeL
)
val v2CoreHex = v2.reversedArray().toHex()
val v3CoreHex = v3.reversedArray().toHex()
check(
v2CoreHex ==
"ba39c447b4577db974473a1e7ed2182686672f2900045a40aee6b4dc5811995b"
) {
"PQR V2 signature hash self-test failed: $v2CoreHex"
}
check(
v3CoreHex ==
"dab3188530f1094ef87087451da77bfba77dfc6e63d1730bb9d0475cebe76618"
) {
"PQR V3 signature hash self-test failed: $v3CoreHex"
}
return v2CoreHex to v3CoreHex
}
private fun createPqWitness( private fun createPqWitness(
signature: ByteArray, signature: ByteArray,
publicKey: ByteArray publicKey: ByteArray
@ -636,290 +566,6 @@ object VextaTransactionSender {
} }
} }
fun runPqWitnessSelfTest(): List<String> {
val seed =
ByteArray(64) { index ->
index.toByte()
}
val mldsa =
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(seed)
)
val sphincs =
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(seed)
)
val message =
"Vexta PQ witness self-test"
.toByteArray(Charsets.UTF_8)
val mldsaSignature =
requireNotNull(
VextaPQ.mldsaSign(
message,
mldsa[1]
)
)
val sphincsSignature =
requireNotNull(
VextaPQ.sphincsSign(
message,
sphincs[1]
)
)
val mldsaWitness =
createPqWitness(
mldsaSignature,
mldsa[0]
)
val sphincsWitness =
createPqWitness(
sphincsSignature,
sphincs[0]
)
check(mldsaWitness.pushCount == 2)
check(mldsaWitness.getPush(0).size == 3309)
check(mldsaWitness.getPush(1).size == 1952)
check(sphincsWitness.pushCount == 2)
check(sphincsWitness.getPush(0).size == 7856)
check(sphincsWitness.getPush(1).size == 32)
mldsa[1].fill(0)
sphincs[1].fill(0)
return listOf(
"MLDSA_WITNESS_PUSHES=${mldsaWitness.pushCount}",
"MLDSA_SIG_BYTES=${mldsaWitness.getPush(0).size}",
"MLDSA_PUBKEY_BYTES=${mldsaWitness.getPush(1).size}",
"SLHDSA_WITNESS_PUSHES=${sphincsWitness.pushCount}",
"SLHDSA_SIG_BYTES=${sphincsWitness.getPush(0).size}",
"SLHDSA_PUBKEY_BYTES=${sphincsWitness.getPush(1).size}",
"PQ_WITNESS_RESULT=PASS"
)
}
fun runPqSignedTransactionSelfTest(): List<String> {
val recipientAddress =
"vtx1z6engqms3emse5lycna6yt7gd6svu7nfd28dccr7mfs8s54pz8rysd6lu5u"
val recipientScript =
addressToScript(recipientAddress)
val changePubKeyHash =
ByteArray(20) { index ->
(0x40 + index).toByte()
}
val changeScript =
byteArrayOf(0x00, 0x14) + changePubKeyHash
val amountSatoshis = 100_000L
val utxoValue = 10_000_000L
fun testType(
addressType: BlockScanner.AddressType,
witnessVersion: Int,
expectedSignatureBytes: Int,
expectedPublicKeyBytes: Int,
txidSuffix: String
): List<String> {
val seed =
ByteArray(64) { index ->
index.toByte()
}
val keyPair =
when (addressType) {
BlockScanner.AddressType.MLDSA ->
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(seed)
)
BlockScanner.AddressType.SLHDSA ->
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(seed)
)
BlockScanner.AddressType.STANDARD ->
error("Standard is not a PQ self-test type")
}
require(keyPair.size == 2)
val publicKey = keyPair[0]
val secretKey = keyPair[1]
val syntheticTxid =
txidSuffix.padStart(64, '0')
val utxo =
BlockScanner.SpendableUtxo(
txid = syntheticTxid,
outputIndex = 0L,
value = utxoValue,
height = 1,
addressIndex = 0,
isCoinbase = false,
addressType = addressType
)
val pqSigningKeys =
mapOf(
(addressType to 0) to
PqSigningKey(
addressType = addressType,
publicKey = publicKey,
secretKey = secretKey
)
)
val created =
createAndSign(
spendableUtxos = listOf(utxo),
recipientAddress = recipientAddress,
amountSatoshis = amountSatoshis,
privateKeysByIndex = emptyMap(),
pqSigningKeys = pqSigningKeys,
changePubKeyHash = changePubKeyHash,
chainHeight = 10_000
)
val parsed =
Transaction(
MainNetParams.get(),
created.rawTransaction
)
check(parsed.inputs.size == 1)
val witness =
parsed.getInput(0L).getWitness()
check(witness.pushCount == 2)
val signature =
witness.getPush(0)
val parsedPublicKey =
witness.getPush(1)
check(signature.size == expectedSignatureBytes)
check(parsedPublicKey.size == expectedPublicKeyBytes)
check(parsedPublicKey.contentEquals(publicKey))
check(
parsed.txId.toString() ==
created.txid
)
val witnessProgram =
MessageDigest
.getInstance("SHA-256")
.digest(publicKey)
val outputs =
mutableListOf(
TransactionOutputData(
value = created.amount,
script = recipientScript
)
)
if (created.change > 0L) {
outputs.add(
TransactionOutputData(
value = created.change,
script = changeScript
)
)
}
val signatureHash =
calculatePqrSignatureHash(
selected = listOf(utxo),
outputs = outputs,
inputIndex = 0,
witnessVersion = witnessVersion,
witnessProgram = witnessProgram,
amount = utxo.value,
version = 2,
lockTime = 0L,
inputSequence = sequence
)
val verified =
when (addressType) {
BlockScanner.AddressType.MLDSA ->
VextaPQ.mldsaVerify(
signature,
signatureHash,
publicKey
)
BlockScanner.AddressType.SLHDSA ->
VextaPQ.sphincsVerify(
signature,
signatureHash,
publicKey
)
BlockScanner.AddressType.STANDARD ->
false
}
check(verified)
seed.fill(0)
signatureHash.fill(0)
return listOf(
"${addressType.name}_SIGNED_TX_BYTES=" +
created.rawTransaction.size,
"${addressType.name}_SIGNED_TX_WITNESS_PUSHES=" +
witness.pushCount,
"${addressType.name}_SIGNED_TX_SIG_BYTES=" +
signature.size,
"${addressType.name}_SIGNED_TX_PUBKEY_BYTES=" +
parsedPublicKey.size,
"${addressType.name}_SIGNED_TX_VERIFY=$verified",
"${addressType.name}_SIGNED_TX_TXID_MATCH=true"
)
}
val results =
mutableListOf<String>()
results +=
testType(
addressType = BlockScanner.AddressType.MLDSA,
witnessVersion = 2,
expectedSignatureBytes = 3309,
expectedPublicKeyBytes = 1952,
txidSuffix = "01"
)
results +=
testType(
addressType = BlockScanner.AddressType.SLHDSA,
witnessVersion = 3,
expectedSignatureBytes = 7856,
expectedPublicKeyBytes = 32,
txidSuffix = "02"
)
results += "PQ_SIGNED_TX_RESULT=PASS"
return results
}
private fun serializeTransaction( private fun serializeTransaction(
selected: List<BlockScanner.SpendableUtxo>, selected: List<BlockScanner.SpendableUtxo>,
outputs: List<TransactionOutputData>, outputs: List<TransactionOutputData>,

View file

@ -352,13 +352,6 @@ class WalletActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (
applicationInfo.flags and
android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE != 0
) {
runPqCompatibilitySelfTest()
}
onBackPressedDispatcher.addCallback( onBackPressedDispatcher.addCallback(
this, this,
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {
@ -445,504 +438,6 @@ class WalletActivity : FragmentActivity() {
} }
} }
private fun runPqCompatibilitySelfTest() {
Thread {
try {
val seed = ByteArray(64) { index ->
index.toByte()
}
val mldsa =
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(seed)
)
val sphincs =
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(seed)
)
require(mldsa.size == 2)
require(sphincs.size == 2)
fun sha256Hex(data: ByteArray): String =
MessageDigest
.getInstance("SHA-256")
.digest(data)
.joinToString("") { byte ->
"%02x".format(byte.toInt() and 0xff)
}
val results = listOf(
"MLDSA_PK" to sha256Hex(mldsa[0]),
"MLDSA_SK" to sha256Hex(mldsa[1]),
"SPHINCS_PK" to sha256Hex(sphincs[0]),
"SPHINCS_SK" to sha256Hex(sphincs[1])
)
results.forEach { (name, hash) ->
android.util.Log.i(
"VextaPQTest",
"$name=$hash"
)
}
val passed =
results[0].second ==
"d666806e11cee19a7c989f7445f90dd419cf4d2d51db8c0fdb4c0f0a542238c9" &&
results[1].second ==
"9f1e24f47795fe50040384e3d6183988047170fa2d866406b70fe0a3f8216063" &&
results[2].second ==
"9d5b69aa74c4939468bae5108568a4d0926f2532c96b0742eda0a7eff4c99c3e" &&
results[3].second ==
"c6222658caa895a591af74d9f8c85d160962d253a782edf95875bbb3acee0a07"
android.util.Log.i(
"VextaPQTest",
if (passed) {
"KEYGEN_RESULT=PASS"
} else {
"KEYGEN_RESULT=FAIL"
}
)
val mldsaKeyId =
MessageDigest
.getInstance("SHA-256")
.digest(mldsa[0])
val sphincsKeyId =
MessageDigest
.getInstance("SHA-256")
.digest(sphincs[0])
val mldsaAddress =
encodeSegwitAddress(
"vtx",
2,
mldsaKeyId
)
val sphincsAddress =
encodeSegwitAddress(
"vtx",
3,
sphincsKeyId
)
android.util.Log.i(
"VextaPQTest",
"MLDSA_ADDRESS=$mldsaAddress"
)
android.util.Log.i(
"VextaPQTest",
"SLHDSA_ADDRESS=$sphincsAddress"
)
val addressPassed =
mldsaAddress ==
"vtx1z6engqms3emse5lycna6yt7gd6svu7nfd28dccr7mfs8s54pz8rysd6lu5u" &&
sphincsAddress ==
"vtx1rn4dkn2n5cjfeg696u5gg269y6zfx7ffje94swshd5zn7laxfnslqfc63ay"
android.util.Log.i(
"VextaPQTest",
"ADDRESS_RESULT=" +
if (addressPassed) "PASS"
else "FAIL"
)
fun derivePqSeedMaterial(
walletSeed: ByteArray,
algorithm: Int,
counter: Int
): ByteArray {
val domain =
"Vexta-PQ-HD-v1"
.toByteArray(Charsets.US_ASCII)
val data = ByteArray(
domain.size + 1 + 4
)
System.arraycopy(
domain,
0,
data,
0,
domain.size
)
var offset = domain.size
data[offset++] =
algorithm.toByte()
data[offset++] =
((counter ushr 24) and 0xff).toByte()
data[offset++] =
((counter ushr 16) and 0xff).toByte()
data[offset++] =
((counter ushr 8) and 0xff).toByte()
data[offset] =
(counter and 0xff).toByte()
val mac =
javax.crypto.Mac.getInstance(
"HmacSHA512"
)
mac.init(
javax.crypto.spec.SecretKeySpec(
walletSeed,
"HmacSHA512"
)
)
return mac.doFinal(data)
}
val pqHdWalletSeed =
ByteArray(32) { index ->
index.toByte()
}
val pqHdVectors = listOf(
"MLDSA_COUNTER_0" to
derivePqSeedMaterial(
pqHdWalletSeed,
2,
0
),
"MLDSA_COUNTER_1" to
derivePqSeedMaterial(
pqHdWalletSeed,
2,
1
),
"SLHDSA_COUNTER_0" to
derivePqSeedMaterial(
pqHdWalletSeed,
3,
0
),
"SLHDSA_COUNTER_1" to
derivePqSeedMaterial(
pqHdWalletSeed,
3,
1
)
)
pqHdVectors.forEach { (name, value) ->
android.util.Log.i(
"VextaPQTest",
"$name=${value.joinToString("") {
"%02x".format(
it.toInt() and 0xff
)
}}"
)
}
val pqHdPassed =
pqHdVectors[0].second.contentEquals(
hexToBytes(
"0b5d81161cee1dd59cba10551b48069b2d38deabaec25ce2553777f2ee1574d8a9b5dd1cc72b84f5a6f97a943a060484b048f7ae06433ef73519c9c47136a8e6"
)
) &&
pqHdVectors[1].second.contentEquals(
hexToBytes(
"96afee1e425cdc5cef6f2a5e565669e059f77538d432b00598c2ce4dae777de58a9ca26ea8c8f63d54a3b9b25554c9db4f4b2f3ff8a83954db44f94c46c7919b"
)
) &&
pqHdVectors[2].second.contentEquals(
hexToBytes(
"62789e270793f5ca638ceba587a63c8931e1fa8f6ae300d79219b56351d71899905391fd9739652b11db87176ecba328cbac551b6b685961afe9c35346ec0d34"
)
) &&
pqHdVectors[3].second.contentEquals(
hexToBytes(
"8e445ae4bb057ce26190c032115adeebbda64f6768904dd56147f4832981fb762e7d683b639c2bff6034c93223f71d3ca3d12bad887cb139d3c62da1f8487fa2"
)
)
android.util.Log.i(
"VextaPQTest",
"PQ_HD_RESULT=" +
if (pqHdPassed) "PASS"
else "FAIL"
)
val message =
"Vexta PQ Android signature test"
.toByteArray(Charsets.UTF_8)
val mldsaSignature =
requireNotNull(
VextaPQ.mldsaSign(
message,
mldsa[1]
)
)
val mldsaVerified =
VextaPQ.mldsaVerify(
mldsaSignature,
message,
mldsa[0]
)
android.util.Log.i(
"VextaPQTest",
"MLDSA_SIG_BYTES=${mldsaSignature.size}"
)
android.util.Log.i(
"VextaPQTest",
"MLDSA_VERIFY=$mldsaVerified"
)
val sphincsSignature =
requireNotNull(
VextaPQ.sphincsSign(
message,
sphincs[1]
)
)
val sphincsVerified =
VextaPQ.sphincsVerify(
sphincsSignature,
message,
sphincs[0]
)
android.util.Log.i(
"VextaPQTest",
"SPHINCS_SIG_BYTES=${sphincsSignature.size}"
)
android.util.Log.i(
"VextaPQTest",
"SPHINCS_VERIFY=$sphincsVerified"
)
java.io.File(
filesDir,
"pq-test-message.bin"
).writeBytes(message)
java.io.File(
filesDir,
"pq-mldsa.sig"
).writeBytes(mldsaSignature)
java.io.File(
filesDir,
"pq-sphincs.sig"
).writeBytes(sphincsSignature)
android.util.Log.i(
"VextaPQTest",
"SIGNATURE_FILES_WRITTEN=true"
)
val signatureTestPassed =
passed &&
mldsaSignature.size == 3309 &&
mldsaVerified &&
sphincsSignature.size == 7856 &&
sphincsVerified
val coreMldsaSignature =
assets.open("pq-core-mldsa.sig").use {
it.readBytes()
}
val coreSphincsSignature =
assets.open("pq-core-sphincs.sig").use {
it.readBytes()
}
val coreMldsaVerified =
VextaPQ.mldsaVerify(
coreMldsaSignature,
message,
mldsa[0]
)
val coreSphincsVerified =
VextaPQ.sphincsVerify(
coreSphincsSignature,
message,
sphincs[0]
)
android.util.Log.i(
"VextaPQTest",
"CORE_TO_ANDROID_MLDSA=$coreMldsaVerified"
)
android.util.Log.i(
"VextaPQTest",
"CORE_TO_ANDROID_SPHINCS=$coreSphincsVerified"
)
val (pqrV2, pqrV3) =
VextaTransactionSender
.runPqrSignatureHashSelfTest()
android.util.Log.i(
"VextaPQTest",
"PQR_V2=$pqrV2"
)
android.util.Log.i(
"VextaPQTest",
"PQR_V3=$pqrV3"
)
val pqrPassed =
pqrV2 ==
"ba39c447b4577db974473a1e7ed2182686672f2900045a40aee6b4dc5811995b" &&
pqrV3 ==
"dab3188530f1094ef87087451da77bfba77dfc6e63d1730bb9d0475cebe76618"
android.util.Log.i(
"VextaPQTest",
"PQR_HASH_RESULT=" +
if (pqrPassed) "PASS"
else "FAIL"
)
val pqWitnessResults =
VextaTransactionSender
.runPqWitnessSelfTest()
pqWitnessResults.forEach { result ->
android.util.Log.i(
"VextaPQTest",
result
)
}
val pqWitnessPassed =
pqWitnessResults.contains(
"PQ_WITNESS_RESULT=PASS"
)
val pqSignedTxResults =
VextaTransactionSender
.runPqSignedTransactionSelfTest()
pqSignedTxResults.forEach { result ->
android.util.Log.i(
"VextaPQTest",
result
)
}
val pqSignedTxPassed =
pqSignedTxResults.contains(
"PQ_SIGNED_TX_RESULT=PASS"
)
val finalPassed =
signatureTestPassed &&
coreMldsaVerified &&
coreSphincsVerified &&
pqrPassed &&
pqWitnessPassed &&
pqSignedTxPassed
android.util.Log.i(
"VextaPQTest",
if (finalPassed) {
"RESULT=PASS"
} else {
"RESULT=FAIL"
}
)
} catch (error: Throwable) {
android.util.Log.e(
"VextaPQTest",
"RESULT=ERROR",
error
)
}
}.start()
}
override fun onResume() {
super.onResume()
if (
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
packageManager.canRequestPackageInstalls()
) {
if (pendingUpdateUri != null) {
openDownloadedUpdate()
}
}
if (
walletExists() &&
mainWalletVisible &&
!blockchainScanRunning
) {
window.decorView.postDelayed({
if (
!isFinishing &&
!isDestroyed &&
mainWalletVisible &&
!blockchainScanRunning
) {
startBackgroundHeaderSyncAndScan()
}
}, 400L)
}
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (!walletExists()) {
super.onBackPressed()
return
}
if (!mainWalletVisible) {
showMainWallet()
return
}
val now = System.currentTimeMillis()
if (now - lastExitSwipeAt <= 2_000L) {
finishAndRemoveTask()
} else {
lastExitSwipeAt = now
Toast.makeText(
this,
"Swipe again to exit",
Toast.LENGTH_SHORT
).show()
}
}
override fun onDestroy() {
refreshHandler.removeCallbacks(refreshRunnable)
try {
unregisterReceiver(updateReceiver)
} catch (_: IllegalArgumentException) {
}
super.onDestroy()
}
private fun createPaymentNotificationChannel() { private fun createPaymentNotificationChannel() {
val manager = val manager =
getSystemService( getSystemService(