From b09ba4c30325ac5cef264eac3567b14b4c5817b0 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Fri, 17 Jul 2026 09:07:35 -0700 Subject: [PATCH] Remove dead, non-functional BizinikiwSr25519Signer stub Never referenced anywhere (self-declaration only, confirmed via grep), sitting since the initial commit. Its RistrettoPoint.multiply()/add() are both no-ops returning `this` unchanged, BASE is all-zero bytes, and keccakF1600() never writes output back into the Strobe state - none of this could ever produce a valid signature. The real, live bizinikiwi signing path is the native JNI binding in bindings/sr25519-bizinikiwi/ (PezkuwiKeyPairSigner -> BizinikiwSr25519), unaffected by this removal. Leaving broken placeholder crypto sitting in a wallet's signer package is its own audit red flag even when provably unused. --- .../signer/BizinikiwSr25519Signer.kt | 315 ------------------ 1 file changed, 315 deletions(-) delete mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/BizinikiwSr25519Signer.kt diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/BizinikiwSr25519Signer.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/BizinikiwSr25519Signer.kt deleted file mode 100644 index 13bcf30d..00000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/BizinikiwSr25519Signer.kt +++ /dev/null @@ -1,315 +0,0 @@ -package io.novafoundation.nova.runtime.extrinsic.signer - -import java.math.BigInteger -import java.nio.ByteBuffer -import java.nio.ByteOrder - -/** - * SR25519 signing implementation for PezkuwiChain using "bizinikiwi" signing context. - * - * This is a port of @pezkuwi/scure-sr25519 which uses Merlin transcripts - * (built on Strobe128) with a custom signing context. - * - * Standard Substrate uses "substrate" context, but Pezkuwi uses "bizinikiwi". - */ -object BizinikiwSr25519Signer { - - private val BIZINIKIWI_CONTEXT = "bizinikiwi".toByteArray(Charsets.UTF_8) - - // Ed25519 curve order - private val CURVE_ORDER = BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989") - - // Strobe128 constants - private const val STROBE_R = 166 - - /** - * Sign a message using SR25519 with "bizinikiwi" context. - * - * @param secretKey 64-byte secret key (32-byte scalar + 32-byte nonce) - * @param message The message to sign - * @return 64-byte signature with Schnorrkel marker - */ - fun sign(secretKey: ByteArray, message: ByteArray): ByteArray { - require(secretKey.size == 64) { "Secret key must be 64 bytes" } - - // Create signing transcript - val transcript = SigningContext("SigningContext") - transcript.label(BIZINIKIWI_CONTEXT) - transcript.bytes(message) - - // Extract key components - val keyScalar = decodeScalar(secretKey.copyOfRange(0, 32)) - val nonce = secretKey.copyOfRange(32, 64) - val publicKey = getPublicKey(secretKey) - val pubPoint = RistrettoPoint.fromBytes(publicKey) - - // Schnorrkel signing protocol - transcript.protoName("Schnorr-sig") - transcript.commitPoint("sign:pk", pubPoint) - - val r = transcript.witnessScalar("signing", nonce) - val R = RistrettoPoint.BASE.multiply(r) - - transcript.commitPoint("sign:R", R) - val k = transcript.challengeScalar("sign:c") - - val s = (k.multiply(keyScalar).add(r)).mod(CURVE_ORDER) - - // Build signature - val signature = ByteArray(64) - System.arraycopy(R.toBytes(), 0, signature, 0, 32) - System.arraycopy(scalarToBytes(s), 0, signature, 32, 32) - - // Add Schnorrkel marker - signature[63] = (signature[63].toInt() or 0x80).toByte() - - return signature - } - - /** - * Get public key from secret key. - */ - fun getPublicKey(secretKey: ByteArray): ByteArray { - require(secretKey.size == 64) { "Secret key must be 64 bytes" } - val scalar = decodeScalar(secretKey.copyOfRange(0, 32)) - return RistrettoPoint.BASE.multiply(scalar).toBytes() - } - - /** - * Verify a signature. - */ - fun verify(message: ByteArray, signature: ByteArray, publicKey: ByteArray): Boolean { - require(signature.size == 64) { "Signature must be 64 bytes" } - require(publicKey.size == 32) { "Public key must be 32 bytes" } - - // Check Schnorrkel marker - if ((signature[63].toInt() and 0x80) == 0) { - return false - } - - // Extract R and s from signature - val sBytes = signature.copyOfRange(32, 64) - sBytes[31] = (sBytes[31].toInt() and 0x7F).toByte() // Remove marker - - val R = RistrettoPoint.fromBytes(signature.copyOfRange(0, 32)) - val s = bytesToScalar(sBytes) - - // Reconstruct transcript - val transcript = SigningContext("SigningContext") - transcript.label(BIZINIKIWI_CONTEXT) - transcript.bytes(message) - - val pubPoint = RistrettoPoint.fromBytes(publicKey) - if (pubPoint.isZero()) return false - - transcript.protoName("Schnorr-sig") - transcript.commitPoint("sign:pk", pubPoint) - transcript.commitPoint("sign:R", R) - - val k = transcript.challengeScalar("sign:c") - - // Verify: R + k*P == s*G - val left = R.add(pubPoint.multiply(k)) - val right = RistrettoPoint.BASE.multiply(s) - - return left == right - } - - private fun decodeScalar(bytes: ByteArray): BigInteger { - require(bytes.size == 32) { "Scalar must be 32 bytes" } - // Little-endian - val reversed = bytes.reversedArray() - return BigInteger(1, reversed).mod(CURVE_ORDER) - } - - private fun bytesToScalar(bytes: ByteArray): BigInteger { - val reversed = bytes.reversedArray() - return BigInteger(1, reversed).mod(CURVE_ORDER) - } - - private fun scalarToBytes(scalar: BigInteger): ByteArray { - val bytes = scalar.toByteArray() - val result = ByteArray(32) - - // Handle sign byte and padding - val start = if (bytes[0] == 0.toByte() && bytes.size > 32) 1 else 0 - val length = minOf(bytes.size - start, 32) - val offset = 32 - length - - System.arraycopy(bytes, start, result, offset, length) - - // Convert to little-endian - return result.reversedArray() - } - - /** - * Strobe128 implementation for Merlin transcripts. - */ - private class Strobe128(protocolLabel: String) { - private val state = ByteArray(200) - private var pos = 0 - private var posBegin = 0 - private var curFlags = 0 - - init { - // Initialize state - state[0] = 1 - state[1] = (STROBE_R + 2).toByte() - state[2] = 1 - state[3] = 0 - state[4] = 1 - state[5] = 96 - - val strobeVersion = "STROBEv1.0.2".toByteArray(Charsets.UTF_8) - System.arraycopy(strobeVersion, 0, state, 6, strobeVersion.size) - - keccakF1600() - metaAD(protocolLabel.toByteArray(Charsets.UTF_8), false) - } - - private fun keccakF1600() { - // Keccak-f[1600] permutation - // Using BouncyCastle's Keccak implementation would be more efficient, - // but for now we'll use a simplified version - val keccak = org.bouncycastle.crypto.digests.KeccakDigest(1600) - keccak.update(state, 0, state.size) - // This is a placeholder - need proper Keccak-p implementation - } - - fun metaAD(data: ByteArray, more: Boolean) { - absorb(data) - } - - fun AD(data: ByteArray, more: Boolean) { - absorb(data) - } - - fun PRF(length: Int): ByteArray { - val result = ByteArray(length) - squeeze(result) - return result - } - - private fun absorb(data: ByteArray) { - for (byte in data) { - state[pos] = (state[pos].toInt() xor byte.toInt()).toByte() - pos++ - if (pos == STROBE_R) { - runF() - } - } - } - - private fun squeeze(out: ByteArray) { - for (i in out.indices) { - out[i] = state[pos] - state[pos] = 0 - pos++ - if (pos == STROBE_R) { - runF() - } - } - } - - private fun runF() { - state[pos] = (state[pos].toInt() xor posBegin).toByte() - state[pos + 1] = (state[pos + 1].toInt() xor 0x04).toByte() - state[STROBE_R + 1] = (state[STROBE_R + 1].toInt() xor 0x80).toByte() - keccakF1600() - pos = 0 - posBegin = 0 - } - } - - /** - * Merlin signing context/transcript. - */ - private class SigningContext(label: String) { - private val strobe = Strobe128(label) - - fun label(data: ByteArray) { - val lengthBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data.size).array() - strobe.metaAD(lengthBytes, false) - strobe.metaAD(data, true) - } - - fun bytes(data: ByteArray) { - val lengthBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data.size).array() - strobe.metaAD(lengthBytes, false) - strobe.AD(data, false) - } - - fun protoName(name: String) { - val data = name.toByteArray(Charsets.UTF_8) - strobe.metaAD(data, false) - } - - fun commitPoint(label: String, point: RistrettoPoint) { - strobe.metaAD(label.toByteArray(Charsets.UTF_8), false) - strobe.AD(point.toBytes(), false) - } - - fun witnessScalar(label: String, nonce: ByteArray): BigInteger { - strobe.metaAD(label.toByteArray(Charsets.UTF_8), false) - strobe.AD(nonce, false) - val bytes = strobe.PRF(64) - return bytesToWideScalar(bytes) - } - - fun challengeScalar(label: String): BigInteger { - strobe.metaAD(label.toByteArray(Charsets.UTF_8), false) - val bytes = strobe.PRF(64) - return bytesToWideScalar(bytes) - } - - private fun bytesToWideScalar(bytes: ByteArray): BigInteger { - // Reduce 64 bytes to a scalar modulo curve order - val reversed = bytes.reversedArray() - return BigInteger(1, reversed).mod(CURVE_ORDER) - } - } - - /** - * Ristretto255 point operations. - * This is a placeholder - full implementation requires Ed25519 curve math. - */ - private class RistrettoPoint private constructor(private val bytes: ByteArray) { - - companion object { - val BASE: RistrettoPoint by lazy { - // Ed25519 base point in Ristretto encoding - val baseBytes = ByteArray(32) - // TODO: Set proper base point bytes - RistrettoPoint(baseBytes) - } - - fun fromBytes(bytes: ByteArray): RistrettoPoint { - require(bytes.size == 32) { "Point must be 32 bytes" } - return RistrettoPoint(bytes.copyOf()) - } - } - - fun toBytes(): ByteArray = bytes.copyOf() - - fun multiply(scalar: BigInteger): RistrettoPoint { - // TODO: Implement scalar multiplication - return this - } - - fun add(other: RistrettoPoint): RistrettoPoint { - // TODO: Implement point addition - return this - } - - fun isZero(): Boolean { - return bytes.all { it == 0.toByte() } - } - - override fun equals(other: Any?): Boolean { - if (other !is RistrettoPoint) return false - return bytes.contentEquals(other.bytes) - } - - override fun hashCode(): Int = bytes.contentHashCode() - } -}