-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: proof of work skeleton/implementations
- Loading branch information
Showing
19 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
.../osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/ProofOfWork.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeMetaData | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeType | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeVerifier | ||
|
||
/** | ||
* Proof of work is a procedure during login to attempt to throttle login requests from a single source, | ||
* by requiring them to do CPU-bound work before accepting the login. | ||
* @property challengeType the type of the challenge to require the client to solve | ||
* @property challengeVerifier the verifier of that challenge, to ensure the client did complete | ||
* the world successfully | ||
*/ | ||
@Suppress("MemberVisibilityCanBePrivate") | ||
public class ProofOfWork<T : ChallengeType<MetaData>, in MetaData : ChallengeMetaData>( | ||
public val challengeType: T, | ||
public val challengeVerifier: ChallengeVerifier<T>, | ||
) |
16 changes: 16 additions & 0 deletions
16
...1-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/ProofOfWorkProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeMetaData | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeType | ||
|
||
/** | ||
* An interface to return proof of work implementations based on the input ip. | ||
*/ | ||
public fun interface ProofOfWorkProvider<T : ChallengeType<MetaData>, in MetaData : ChallengeMetaData> { | ||
/** | ||
* Provides a proof of work instance for a given [ip]. | ||
* @param ip the IP from which the client is connecting. | ||
* @return a proof of work instance that the client needs to solve. | ||
*/ | ||
public fun provide(ip: Int): ProofOfWork<T, MetaData> | ||
} |
29 changes: 29 additions & 0 deletions
29
...c/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/SingleTypeProofOfWorkProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeGenerator | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeMetaData | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeMetaDataProvider | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeType | ||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeVerifier | ||
|
||
/** | ||
* A single type proof of work provider is used to always return proof of work instances | ||
* of a single specific type. | ||
* @property metaDataProvider the provider used to return instances of metadata for the | ||
* challenges. | ||
* @property challengeGenerator the generator that will create a new proof of work challenge | ||
* based on the input metadata. | ||
* @property challengeVerifier the verifier that will check if the answer sent by the client | ||
* is correct. | ||
*/ | ||
public class SingleTypeProofOfWorkProvider<T : ChallengeType<MetaData>, in MetaData : ChallengeMetaData>( | ||
private val metaDataProvider: ChallengeMetaDataProvider<MetaData>, | ||
private val challengeGenerator: ChallengeGenerator<MetaData, T>, | ||
private val challengeVerifier: ChallengeVerifier<T>, | ||
) : ProofOfWorkProvider<T, MetaData> { | ||
override fun provide(ip: Int): ProofOfWork<T, MetaData> { | ||
val metadata = metaDataProvider.provide(ip) | ||
val challenge = challengeGenerator.generate(metadata) | ||
return ProofOfWork(challenge, challengeVerifier) | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...c/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
/** | ||
* A challenge generator used to construct a challenge out of the provided metadata. | ||
*/ | ||
public fun interface ChallengeGenerator<in MetaData : ChallengeMetaData, out Type : ChallengeType<MetaData>> { | ||
/** | ||
* A function to generate a challenge out of the provided metadata. | ||
* @param input the metadata input necessary to generate a challenge | ||
* @return a challenge generated out of the metadata | ||
*/ | ||
public fun generate(input: MetaData): Type | ||
} |
6 changes: 6 additions & 0 deletions
6
...rc/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeMetaData.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
/** | ||
* A common binding interface for metadata necessary to pass into the challenge constructors. | ||
*/ | ||
public interface ChallengeMetaData |
15 changes: 15 additions & 0 deletions
15
...kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeMetaDataProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
/** | ||
* A challenge metadata provider is used to generate a metadata necessary to construct a challenge. | ||
*/ | ||
public fun interface ChallengeMetaDataProvider<out T : ChallengeMetaData> { | ||
/** | ||
* Provides a metadata instance for a challenge, using the ip as the input parameter. | ||
* @param ip the IP from which the user is connecting to the server. | ||
* This is provided in case an implementation which scales with the number of requests | ||
* from a given host is desired. | ||
* @return the metadata object necessary to construct a challenge. | ||
*/ | ||
public fun provide(ip: Int): T | ||
} |
16 changes: 16 additions & 0 deletions
16
...el/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
/** | ||
* A common binding interface for challenge types. | ||
* Currently, the client only supports SHA-256 as a challenge, but it is set up to | ||
* support other types with ease. | ||
* @param MetaData the metadata necessary to construct a challenge of this type. | ||
* @property id the id of the challenge, used by the client to identify what challenge | ||
* solver to use. | ||
* @property resultSize the number of bytes the server must have in the socket before | ||
* it can attempt to verify the challenge. | ||
*/ | ||
public interface ChallengeType<in MetaData : ChallengeMetaData> { | ||
public val id: Int | ||
public val resultSize: Int | ||
} |
24 changes: 24 additions & 0 deletions
24
...rc/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeVerifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
import net.rsprot.buffer.JagByteBuf | ||
|
||
/** | ||
* A challenge verifier is used to check the work that the client did. | ||
* The general idea here is that the client has to perform the work N times, where N is | ||
* pseudo-random, while the server only has to do that same work one time - to verify the | ||
* result that the client sent. The complexity of the work to perform is configurable by the | ||
* server. | ||
* @param T the challenge type to verify | ||
*/ | ||
public interface ChallengeVerifier<in T : ChallengeType<*>> { | ||
/** | ||
* Verifies the work performed by the client. | ||
* @param result the byte buffer containing the result sent by the client. | ||
* @param challenge the challenge to verify using the [result] provided. | ||
* @return whether the challenge is solved using the [result] provided. | ||
*/ | ||
public fun verify( | ||
result: JagByteBuf, | ||
challenge: T, | ||
): Boolean | ||
} |
26 changes: 26 additions & 0 deletions
26
.../src/main/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/ChallengeWorker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
import net.rsprot.buffer.JagByteBuf | ||
import java.util.concurrent.CompletableFuture | ||
|
||
/** | ||
* A worker is used to perform the verifications of the data sent by the client for our | ||
* proof of work requests. While the work itself is relatively cheap, servers may wish | ||
* to perform the work on other threads - this interface allows doing that. | ||
*/ | ||
public interface ChallengeWorker { | ||
/** | ||
* Verifies the result sent by the client. | ||
* @param result the byte buffer containing the result data sent by the client | ||
* @param challenge the challenge the client had to solve | ||
* @param verifier the verifier used to check the work done by the client for out challenge | ||
* @return a future object containing the result of the work, or an exception. | ||
* If the future doesn't return immediately, there will be a 30-second timeout applied to it, | ||
* after which the work will be concluded failed. | ||
*/ | ||
public fun <T : ChallengeType<*>, V : ChallengeVerifier<T>> verify( | ||
result: JagByteBuf, | ||
challenge: T, | ||
verifier: V, | ||
): CompletableFuture<Boolean> | ||
} |
23 changes: 23 additions & 0 deletions
23
...in/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/DefaultChallengeWorker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges | ||
|
||
import net.rsprot.buffer.JagByteBuf | ||
import java.util.concurrent.CompletableFuture | ||
|
||
/** | ||
* The default challenge worker will perform the work on the calling thread. | ||
* The SHA-256 challenges are fairly inexpensive and the overhead of switching threads | ||
* is similar to the work itself done. | ||
*/ | ||
public data object DefaultChallengeWorker : ChallengeWorker { | ||
override fun <T : ChallengeType<*>, V : ChallengeVerifier<T>> verify( | ||
result: JagByteBuf, | ||
challenge: T, | ||
verifier: V, | ||
): CompletableFuture<Boolean> { | ||
return try { | ||
CompletableFuture.completedFuture(verifier.verify(result, challenge)) | ||
} catch (t: Throwable) { | ||
CompletableFuture.failedFuture(t) | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
...prot/protocol/loginprot/incoming/pow/challenges/sha256/DefaultSha256ChallengeGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges.sha256 | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeGenerator | ||
import java.math.BigInteger | ||
import kotlin.random.Random | ||
|
||
/** | ||
* The default SHA-256 challenge generator is used to generate challenges which align | ||
* up with what OldSchool RuneScape is generating, which is a combination of epoch time millis, | ||
* the world id and a 495-byte [BigInteger] that is turned into a hexadecimal string, | ||
* which will have a length of 1004 or 1005 characters, depending on if the [BigInteger] was negative. | ||
*/ | ||
public class DefaultSha256ChallengeGenerator : | ||
ChallengeGenerator<Sha256MetaData, Sha256Challenge> { | ||
override fun generate(input: Sha256MetaData): Sha256Challenge { | ||
val randomData = Random.Default.nextBytes(RANDOM_DATA_LENGTH) | ||
val hexSalt = BigInteger(randomData).toString(HEX_RADIX) | ||
val salt = | ||
java.lang.Long.toHexString(input.epochTimeMillis) + | ||
Integer.toHexString(input.world) + | ||
hexSalt | ||
return Sha256Challenge( | ||
input.unknown, | ||
input.difficulty, | ||
salt, | ||
) | ||
} | ||
|
||
private companion object { | ||
private const val RANDOM_DATA_LENGTH: Int = 495 | ||
private const val HEX_RADIX: Int = 16 | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...rsprot/protocol/loginprot/incoming/pow/challenges/sha256/DefaultSha256MetaDataProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges.sha256 | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeMetaDataProvider | ||
|
||
/** | ||
* The default SHA-256 metadata provider will return a metadata object | ||
* that matches what OldSchool RuneScape sends. | ||
* @property world the world that the client is connecting to. | ||
*/ | ||
public class DefaultSha256MetaDataProvider( | ||
private val world: Int, | ||
) : ChallengeMetaDataProvider<Sha256MetaData> { | ||
override fun provide(ip: Int): Sha256MetaData { | ||
return Sha256MetaData(world) | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...rot/protocol/loginprot/incoming/pow/challenges/sha256/DefaultSha256ProofOfWorkProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges.sha256 | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.ProofOfWorkProvider | ||
import net.rsprot.protocol.loginprot.incoming.pow.SingleTypeProofOfWorkProvider | ||
|
||
/** | ||
* A value class to wrap the properties of a SHA-256 into a single instance. | ||
* @property provider the SHA-256 proof of work provider. | ||
*/ | ||
@Suppress("MemberVisibilityCanBePrivate") | ||
@JvmInline | ||
public value class DefaultSha256ProofOfWorkProvider private constructor( | ||
public val provider: SingleTypeProofOfWorkProvider<Sha256Challenge, Sha256MetaData>, | ||
) : ProofOfWorkProvider<Sha256Challenge, Sha256MetaData> by provider { | ||
public constructor( | ||
world: Int, | ||
) : this( | ||
SingleTypeProofOfWorkProvider( | ||
DefaultSha256MetaDataProvider(world), | ||
DefaultSha256ChallengeGenerator(), | ||
Sha256ChallengeVerifier(), | ||
), | ||
) | ||
} |
75 changes: 75 additions & 0 deletions
75
...in/kotlin/net/rsprot/protocol/loginprot/incoming/pow/challenges/sha256/Sha256Challenge.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package net.rsprot.protocol.loginprot.incoming.pow.challenges.sha256 | ||
|
||
import net.rsprot.protocol.loginprot.incoming.pow.challenges.ChallengeType | ||
|
||
/** | ||
* A SHA-256 challenge is a challenge which forces the client to find a hash which | ||
* has at least [difficulty] number of leading zero bits in the hash. | ||
* As hashing returns pseudo-random results, as a general rule of thumb, the work | ||
* needed to solve a challenge doubles with each difficulty increase, since each | ||
* bit can be either true or false, and the solution must have at least [difficulty] | ||
* amount of false (zero) bits. | ||
* Since the requirement is that there are at least [difficulty] amount of leading | ||
* zero bits, these challenges aren't constrained to only having a single successful | ||
* answer. | ||
* @property unknown an unknown byte value that is appended to the start of each | ||
* base string that needs hashing. The value of this byte is **always** one in OldSchool | ||
* RuneScape. | ||
* @property difficulty the difficulty of the challenge, as explained above, is the number | ||
* of leading zero bits the hash must have for it to be considered successful. | ||
* The default difficulty in OldSchool RuneScape is 18 as of writing this. | ||
* When Proof of Work was first introduced, this value was 16. | ||
* It is possible that the value gets dynamically increased as the pressure increases, | ||
* or if there are a lot of requests from a single IP. | ||
* @property salt the salt string that is the bulk of the input to hash. | ||
* @property id the id of the challenge as identified by the client. | ||
* @property resultSize the number of bytes the server must have in its socket after sending | ||
* a SHA-256 challenge request, in order to attempt to verify it. | ||
*/ | ||
public class Sha256Challenge( | ||
public val unknown: Int, | ||
public val difficulty: Int, | ||
public val salt: String, | ||
) : ChallengeType<Sha256MetaData> { | ||
override val id: Int | ||
get() = 0 | ||
override val resultSize: Int | ||
get() = Long.SIZE_BYTES | ||
|
||
/** | ||
* Gets the base string that is part of the input for the hash. | ||
* A long will be appended to this base string at the end, which will additionally | ||
* be the solution to the challenge. The full string of baseString + the long is what | ||
* must result in [difficulty] number of leading zero bits after having been hashed. | ||
* @return the base string used for the hashing input. | ||
*/ | ||
public fun getBaseString(): String { | ||
return Integer.toHexString(this.unknown) + Integer.toHexString(this.difficulty) + this.salt | ||
} | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (other !is Sha256Challenge) return false | ||
|
||
if (unknown != other.unknown) return false | ||
if (difficulty != other.difficulty) return false | ||
if (salt != other.salt) return false | ||
|
||
return true | ||
} | ||
|
||
override fun hashCode(): Int { | ||
var result = unknown | ||
result = 31 * result + difficulty | ||
result = 31 * result + salt.hashCode() | ||
return result | ||
} | ||
|
||
override fun toString(): String { | ||
return "Sha256Challenge(" + | ||
"unknown=$unknown, " + | ||
"difficulty=$difficulty, " + | ||
"salt='$salt'" + | ||
")" | ||
} | ||
} |
Oops, something went wrong.