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.
This commit is contained in:
2026-07-17 09:07:35 -07:00
parent 16427654ae
commit b09ba4c303
@@ -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()
}
}