mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 14:55:48 +00:00
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:
-315
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user