feat: Add bizinikiwi signing context for Pezkuwi ecosystem

Pezkuwi chains use "bizinikiwi" signing context instead of standard
"substrate" context used by Polkadot ecosystem. This was causing
"bad signature" errors on HEZ transfers.

Changes:
- Add sr25519-bizinikiwi native Rust binding with bizinikiwi context
- Add isPezkuwiChain extension to detect Pezkuwi ecosystem chains
- Add PezkuwiKeyPairSigner for signing with bizinikiwi context
- Modify SecretsSigner to use correct context based on chain:
  - Pezkuwi chains (3) -> BizinikiwSr25519 (bizinikiwi)
  - Other chains (98+) -> KeyPairSigner (substrate)
- Add live transfer test for Pezkuwi mainnet

Tested with successful HEZ transfer on Pezkuwi mainnet:
TX: 0xe25a4eaaeaa04122cca130582dba3cacb2280dea5d908924b45757ea67c27996
This commit is contained in:
2026-02-07 03:41:52 +03:00
parent 7542046890
commit c12dd79c74
1354 changed files with 86971 additions and 6 deletions
@@ -7,20 +7,29 @@ import io.novafoundation.nova.common.data.secrets.v2.getMetaAccountKeypair
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.sequrity.TwoFactorVerificationResult
import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService
import io.novafoundation.nova.feature_account_api.data.signer.SigningContext
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.ethereumAccountId
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdKeyIn
import io.novafoundation.nova.feature_account_api.domain.model.substrateFrom
import io.novafoundation.nova.feature_account_impl.data.signer.LeafSigner
import io.novafoundation.nova.runtime.ext.isPezkuwiChain
import io.novafoundation.nova.runtime.extrinsic.signer.PezkuwiKeyPairSigner
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.ChainsById
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chainsById
import io.novasama.substrate_sdk_android.encrypt.MultiChainEncryption
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.encrypt.keypair.Keypair
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.KeyPairSigner
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.CheckNonce.Companion.setNonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.verifySignature.VerifySignature.Companion.setVerifySignature
import javax.inject.Inject
@FeatureScope
@@ -47,21 +56,63 @@ class SecretsSigner(
private val twoFactorVerificationService: TwoFactorVerificationService,
) : LeafSigner(metaAccount) {
// Track current signing chain to determine which context to use
@Volatile
private var currentSigningChain: Chain? = null
context(ExtrinsicBuilder)
override suspend fun setSignerDataForSubmission(context: SigningContext) {
// Capture the chain for use in signInheritedImplication
currentSigningChain = context.chain
val accountId = metaAccount.requireAccountIdKeyIn(context.chain)
setNonce(context.getNonce(accountId))
setVerifySignature(signer = this, accountId = accountId.value)
}
context(ExtrinsicBuilder)
override suspend fun setSignerDataForFee(context: SigningContext) {
// Capture the chain for use in signInheritedImplication
currentSigningChain = context.chain
// Call parent implementation for fee signing
super.setSignerDataForFee(context)
}
override suspend fun signInheritedImplication(
inheritedImplication: InheritedImplication,
accountId: AccountId
): SignatureWrapper {
runTwoFactorVerificationIfEnabled()
val delegate = createDelegate(accountId)
return delegate.signInheritedImplication(inheritedImplication, accountId)
val chain = currentSigningChain
val keypair = getKeypair(accountId)
// Use PezkuwiKeyPairSigner for Pezkuwi chains (bizinikiwi context)
// Use standard KeyPairSigner for other chains (substrate context)
return if (chain?.isPezkuwiChain == true) {
val pezkuwiSigner = PezkuwiKeyPairSigner(keypair)
pezkuwiSigner.signInheritedImplication(inheritedImplication, accountId)
} else {
val delegate = createDelegate(accountId, keypair)
delegate.signInheritedImplication(inheritedImplication, accountId)
}
}
override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw {
runTwoFactorVerificationIfEnabled()
val delegate = createDelegate(payload.accountId)
return delegate.signRaw(payload)
val chain = currentSigningChain
val keypair = getKeypair(payload.accountId)
// Use PezkuwiKeyPairSigner for Pezkuwi chains (bizinikiwi context)
return if (chain?.isPezkuwiChain == true) {
val pezkuwiSigner = PezkuwiKeyPairSigner(keypair)
pezkuwiSigner.signRaw(payload)
} else {
val delegate = createDelegate(payload.accountId, keypair)
delegate.signRaw(payload)
}
}
override suspend fun maxCallsPerTransaction(): Int? {
@@ -77,16 +128,20 @@ class SecretsSigner(
}
}
private suspend fun createDelegate(accountId: AccountId): KeyPairSigner {
private suspend fun getKeypair(accountId: AccountId): Keypair {
val chainsById = chainRegistry.chainsById()
val multiChainEncryption = metaAccount.multiChainEncryptionFor(accountId, chainsById)!!
val keypair = secretStoreV2.getKeypair(
return secretStoreV2.getKeypair(
metaAccount = metaAccount,
accountId = accountId,
isEthereumBased = multiChainEncryption is MultiChainEncryption.Ethereum
)
}
private suspend fun createDelegate(accountId: AccountId, keypair: Keypair): KeyPairSigner {
val chainsById = chainRegistry.chainsById()
val multiChainEncryption = metaAccount.multiChainEncryptionFor(accountId, chainsById)!!
return KeyPairSigner(keypair, multiChainEncryption)
}