Skip to content

Commit

Permalink
Add txn build, sign, and submission features
Browse files Browse the repository at this point in the history
- Implemented txn construction with customizable parameters.
- Integrated signing (JVM and Android).
- Added txn submission.
- Enhanced error handling and validation.
  • Loading branch information
astinz committed Jun 13, 2024
1 parent 3e50e5f commit 60eb815
Show file tree
Hide file tree
Showing 41 changed files with 1,213 additions and 41 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ compose = "1.6.5"
compose-plugin = "1.6.1"
junit = "4.13.2"
kotlin = "1.9.23"
kotlinxDatetime = "0.6.0"
kotlinxSerializationCore = "1.6.3"
ktor = "2.3.10"
kaptos = "1.0.1-SNAPSHOT"
Expand All @@ -37,6 +38,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
kaptos = { module = "xyz.mcxross.kaptos:kaptos", version.ref = "kaptos" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }
ktor-client-cio = { module = "io.ktor:ktor-client-apache", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
Expand Down
1 change: 1 addition & 0 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ kotlin {
implementation(libs.ktor.client.auth)
implementation(libs.kotlinx.serialization.core)
implementation(libs.bcs)
implementation(libs.kotlinx.datetime)
}
commonTest.dependencies {
implementation(kotlin("test"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2024 McXross
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.mcxross.kaptos.core.crypto

import io.ktor.util.reflect.*
import xyz.mcxross.bcs.Bcs
import xyz.mcxross.kaptos.model.AnyRawTransaction
import xyz.mcxross.kaptos.transaction.builder.deriveTransactionType
import xyz.mcxross.kaptos.transaction.instances.RawTransaction
import xyz.mcxross.kaptos.util.RAW_TRANSACTION_SALT

actual fun generateSigningMessage(transaction: AnyRawTransaction): ByteArray {
val rawTxn = deriveTransactionType(transaction)
val hash = org.bouncycastle.jcajce.provider.digest.SHA3.Digest256()

if (transaction.instanceOf(RawTransaction::class)) {
hash.update(RAW_TRANSACTION_SALT.encodeToByteArray())
}

val body = Bcs.encodeToByteArray<RawTransaction>(rawTxn as RawTransaction)

return hash.digest() + body
}

actual fun sign(message: ByteArray, privateKey: ByteArray): ByteArray {
val signer = org.bouncycastle.crypto.signers.Ed25519Signer()
val privateKeyParameters =
org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters(privateKey, 0)
signer.init(true, privateKeyParameters)
signer.update(message, 0, message.size)
return signer.generateSignature()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package xyz.mcxross.kaptos.core.crypto

import xyz.mcxross.kaptos.model.AnyRawTransaction
import xyz.mcxross.kaptos.model.SigningScheme

actual fun generateKeypair(scheme: SigningScheme): KeyPair {
Expand All @@ -29,3 +30,11 @@ actual fun fromSeed(seed: ByteArray): KeyPair {
actual fun sha3Hash(input: ByteArray): ByteArray {
TODO("Not yet implemented")
}

actual fun generateSigningMessage(transaction: AnyRawTransaction): ByteArray {
TODO("Not yet implemented")
}

actual fun sign(message: ByteArray, privateKey: ByteArray): ByteArray {
TODO("Not yet implemented")
}
19 changes: 15 additions & 4 deletions lib/src/commonMain/kotlin/xyz/mcxross/kaptos/api/Transaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ package xyz.mcxross.kaptos.api
import xyz.mcxross.kaptos.internal.getGasPriceEstimation
import xyz.mcxross.kaptos.internal.getTransactionByHash
import xyz.mcxross.kaptos.internal.getTransactionByVersion
import xyz.mcxross.kaptos.model.AptosConfig
import xyz.mcxross.kaptos.model.GasEstimation
import xyz.mcxross.kaptos.model.Option
import xyz.mcxross.kaptos.model.TransactionResponse
import xyz.mcxross.kaptos.internal.signTransaction
import xyz.mcxross.kaptos.model.*
import xyz.mcxross.kaptos.protocol.Transaction
import xyz.mcxross.kaptos.transaction.authenticatior.AccountAuthenticator

/**
* Transaction API namespace. This class provides functionality to reading and writing transaction
Expand Down Expand Up @@ -68,4 +67,16 @@ class Transaction(val config: AptosConfig) : Transaction {
throw Exception("Failed to get gas price estimation")
}
}

/**
* Sign a transaction that can later be submitted to chain
*
* @param signer The signer account
* @param transaction A raw transaction to sign on
* @returns [AccountAuthenticator]
*/
override fun sign(
signer: xyz.mcxross.kaptos.core.account.Account,
transaction: AnyRawTransaction,
): AccountAuthenticator = signTransaction(signer, transaction)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024 McXross
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.mcxross.kaptos.api.txsubmission

import xyz.mcxross.kaptos.internal.generateTransaction
import xyz.mcxross.kaptos.model.*

/** A class to handle all `Build` transaction operations */
class Build(val config: AptosConfig) {

/**
* Build a simple transaction
*
* @param sender The sender account address
* @param data The transaction data
* @param options optional. Optional transaction configurations
* @param withFeePayer optional. Whether there is a fee payer for the transaction
* @returns [SimpleTransaction]
*/
suspend fun simple(
sender: AccountAddressInput,
data: InputGenerateTransactionPayloadData,
options: InputGenerateTransactionOptions? = null,
withFeePayer: Boolean = false,
): SimpleTransaction {
val singleSignerRawTransactionData =
InputGenerateSingleSignerRawTransactionData(
sender = sender,
data = data,
options = options,
withFeePayer = withFeePayer,
secondarySignerAddresses = null,
)
return generateTransaction(config, singleSignerRawTransactionData) as SimpleTransaction
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 McXross
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.mcxross.kaptos.api.txsubmission

import xyz.mcxross.kaptos.internal.submitTransaction
import xyz.mcxross.kaptos.model.*
import xyz.mcxross.kaptos.transaction.authenticatior.AccountAuthenticator

class Submit(private val aptosConfig: AptosConfig) {

/**
* Submit a simple transaction
*
* @param transaction An instance of a raw transaction
* @param senderAuthenticator optional. The sender account authenticator
* @param feePayerAuthenticator optional. The fee payer account authenticator if it is a fee payer
* transaction
* @returns PendingTransactionResponse
*/
suspend fun simple(
transaction: AnyRawTransaction,
senderAuthenticator: AccountAuthenticator,
feePayerAuthenticator: AccountAuthenticator? = null,
): Option<PendingTransactionResponse> =
submitTransaction(
aptosConfig = aptosConfig,
InputSubmitTransactionData(transaction, senderAuthenticator, feePayerAuthenticator),
)
}
2 changes: 2 additions & 0 deletions lib/src/commonMain/kotlin/xyz/mcxross/kaptos/core/Hex.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package xyz.mcxross.kaptos.core

import kotlinx.serialization.Serializable
import xyz.mcxross.kaptos.exception.ParsingException
import xyz.mcxross.kaptos.model.HexInput

Expand All @@ -36,6 +37,7 @@ enum class HexInvalidReason(val reason: String) {
* @constructor Creates a hex string from a string.
* @property data The data of the hex string.
*/
@Serializable
class Hex() {
private var data: ByteArray = byteArrayOf()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import xyz.mcxross.kaptos.core.crypto.PrivateKey
import xyz.mcxross.kaptos.core.crypto.PublicKey
import xyz.mcxross.kaptos.core.crypto.Signature
import xyz.mcxross.kaptos.model.*
import xyz.mcxross.kaptos.transaction.authenticatior.AccountAuthenticator

abstract class Account {

Expand All @@ -32,6 +33,15 @@ abstract class Account {
/** Signing scheme used to sign transactions */
abstract val signingScheme: SigningScheme

/**
* Sign a message using the available signing capabilities.
*
* @param message the signing message, as binary input
* @return the [AccountAuthenticator] containing the signature, together with the account's public
* key
*/
abstract fun signWithAuthenticator(message: HexInput): AccountAuthenticator

/**
* Sign the given message with the private key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
*/
package xyz.mcxross.kaptos.core.account

import xyz.mcxross.kaptos.model.AccountAddress
import xyz.mcxross.kaptos.model.AccountAddressInput
import xyz.mcxross.kaptos.model.HexInput
import xyz.mcxross.kaptos.core.crypto.Ed25519PrivateKey
import xyz.mcxross.kaptos.core.crypto.Ed25519PublicKey
import xyz.mcxross.kaptos.core.crypto.Ed25519Signature
import xyz.mcxross.kaptos.core.crypto.Signature
import xyz.mcxross.kaptos.model.AccountAddress
import xyz.mcxross.kaptos.model.AccountAddressInput
import xyz.mcxross.kaptos.model.HexInput
import xyz.mcxross.kaptos.model.SigningScheme
import xyz.mcxross.kaptos.transaction.authenticatior.AccountAuthenticator
import xyz.mcxross.kaptos.transaction.authenticatior.AccountAuthenticatorEd25519

/**
* Signer implementation for the Ed25519 authentication scheme. This extends an [Ed25519Account] by
Expand Down Expand Up @@ -51,7 +52,7 @@ class Ed25519Account(val privateKey: Ed25519PrivateKey, val address: AccountAddr
get() = SigningScheme.Ed25519

init {
this.publicKey = privateKey.publicKey() as Ed25519PublicKey
this.publicKey = privateKey.publicKey()
this.accountAddress =
if (address != null) {
AccountAddress.from(address)
Expand All @@ -60,13 +61,20 @@ class Ed25519Account(val privateKey: Ed25519PrivateKey, val address: AccountAddr
}
}

fun signWithAuthenticator(message: HexInput): Ed25519Signature {
/**
* Sign a message using the available signing capabilities.
*
* @param message the signing message, as binary input
* @return the [AccountAuthenticator] containing the signature, together with the account's public
* key
*/
override fun signWithAuthenticator(message: HexInput): AccountAuthenticator {
val signature = this.privateKey.sign(message)
TODO("Not yet implemented: We should call the actual Ed25519 sign")
return AccountAuthenticatorEd25519(this.publicKey, signature)
}

override fun sign(message: HexInput): Signature {
return this.signWithAuthenticator(message)
return (this.signWithAuthenticator(message) as AccountAuthenticatorEd25519).signature
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package xyz.mcxross.kaptos.core.crypto

import kotlinx.serialization.Serializable
import xyz.mcxross.kaptos.core.AuthenticationKey
import xyz.mcxross.kaptos.core.Hex
import xyz.mcxross.kaptos.model.AuthenticationKeyScheme
Expand All @@ -30,7 +31,8 @@ import xyz.mcxross.kaptos.model.SigningScheme
* Ed25519 scheme is represented in the SDK as `Legacy authentication key` and also as
* `AnyPublicKey` that represents any `Unified authentication key`
*/
class Ed25519PublicKey(data: HexInput) : AccountPublicKey() {
@Serializable
class Ed25519PublicKey(private val data: HexInput) : AccountPublicKey() {

private var hex: Hex

Expand Down Expand Up @@ -97,9 +99,9 @@ class Ed25519PrivateKey(data: HexInput) : PrivateKey {
* @param message in HexInput format
* @return [Signature]
*/
override fun sign(message: HexInput): Signature {
override fun sign(message: HexInput): Ed25519Signature {
val messageBytes = Hex.fromHexInput(message).toByteArray()
return signingKeyPair.sign(messageBytes)
return signingKeyPair.sign(messageBytes) as Ed25519Signature
}

/**
Expand Down Expand Up @@ -137,7 +139,8 @@ class Ed25519PrivateKey(data: HexInput) : PrivateKey {
}

/** A signature of a message signed using an Ed25519 private key */
class Ed25519Signature(hexInput: HexInput) : Signature() {
@Serializable
class Ed25519Signature(private val hexInput: HexInput) : Signature() {

private var data: Hex

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package xyz.mcxross.kaptos.core.crypto

import xyz.mcxross.kaptos.model.HexInput

/**
* Represents a pair of signing keys: a public key and a private key.
*
Expand All @@ -24,7 +26,8 @@ package xyz.mcxross.kaptos.core.crypto
data class KeyPair(val privateKey: ByteArray, val publicKey: ByteArray) {

fun sign(message: ByteArray): Signature {
TODO()
val sigBytes = sign(message, privateKey)
return Ed25519Signature(HexInput.fromByteArray(sigBytes))
}

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
*/
package xyz.mcxross.kaptos.core.crypto

import xyz.mcxross.kaptos.model.AnyRawTransaction
import xyz.mcxross.kaptos.model.SigningScheme

expect fun generateKeypair(scheme: SigningScheme) : KeyPair
expect fun generateKeypair(scheme: SigningScheme): KeyPair

expect fun fromSeed(seed: ByteArray): KeyPair

expect fun sha3Hash(input: ByteArray): ByteArray
expect fun sha3Hash(input: ByteArray): ByteArray

expect fun sign(message: ByteArray, privateKey: ByteArray): ByteArray

expect fun generateSigningMessage(transaction: AnyRawTransaction): ByteArray
Loading

0 comments on commit 60eb815

Please sign in to comment.