obfs4 for Android, part 1: Elligator2, against the same vectors as Swift
A new `:obfs4` module, a plain JVM library like `:engine` so its tests run without an emulator -- which matters here because the only tests that mean anything are vector comparisons against lyrebird. Far smaller than the Swift port, for a reason worth recording: the Swift implementation hand-writes SipHash-2-4, XSalsa20, Poly1305 and its own field arithmetic because CryptoKit exposes none of them. The JVM does not have that hole. BouncyCastle -- already a dependency of :engine, for the same class of reason -- has X25519Field, XSalsa20Engine, Poly1305 and SipHash, vetted and constant-time where it matters. So the two clients do NOT share an implementation. They share the vectors and the live interop harness, which is what actually keeps them honest: two independent implementations agreeing with the same reference is a stronger statement than one agreeing with itself twice. Two things BouncyCastle would not do, both found by vectors rather than by reading. X25519Field.sqrtRatioVar is not a drop-in. Elligator needs the root even when the ratio is NOT square -- it is sqrt(i*u/v) then, and the algorithm uses it to pick a branch rather than to detect an error. BC only promises a usable root in the square case, and handing its output to the map produced zeros for 120 of 256 vectors. The exponent chain and branch selection are written out here; the field arithmetic underneath is still BouncyCastle's. X25519Field.cmov did nothing at all with a condition of 1. Both tweak parities produced the SAME representative -- a map that still round-trips, still looks uniform, and only ever emits one of the two valid answers. The failing assertion said "wrong bytes"; it took a diagnostic printing both candidates to see they were identical. Replaced with an explicit mask so the convention is stated rather than assumed. Behind no setting yet because nothing calls it: this is the crypto foundation only. The transport, the OkHttp socket factory and the user-facing switch come next, and the switch defaults to off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
945978972a
commit
db35c7ce55
@@ -0,0 +1,47 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
jacoco
|
||||
}
|
||||
|
||||
repositories { mavenCentral() }
|
||||
|
||||
dependencies {
|
||||
// 🔴 **The whole reason this port is small.** The Swift implementation had
|
||||
// to hand-write SipHash-2-4, XSalsa20 and Poly1305 because CryptoKit
|
||||
// exposes none of them, and its own field arithmetic because Apple gives
|
||||
// nothing below X25519. BouncyCastle has all four, vetted and
|
||||
// constant-time where it matters: `X25519Field` (including `sqrtRatioVar`,
|
||||
// exactly the primitive Elligator2 needs), `XSalsa20Engine`, `Poly1305`.
|
||||
//
|
||||
// ⚠️ That means the two clients do NOT share an implementation. They share
|
||||
// the vectors and the live interop harness, which is what actually keeps
|
||||
// them honest — see `Obfs4VectorTest`.
|
||||
//
|
||||
// Already a dependency of `:engine`, for the same class of reason: taking a
|
||||
// vetted constant-time implementation over hand-rolling arithmetic on a
|
||||
// secret scalar.
|
||||
implementation("org.bouncycastle:bcprov-jdk18on:1.79")
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation("org.json:json:20240303")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
testLogging { events("passed", "failed", "skipped"); showStandardStreams = true }
|
||||
}
|
||||
tasks.withType<Test>().configureEach { finalizedBy(tasks.named("jacocoTestReport")) }
|
||||
tasks.named<JacocoReport>("jacocoTestReport") {
|
||||
dependsOn(tasks.named("test"))
|
||||
reports { xml.required.set(true); html.required.set(true) }
|
||||
}
|
||||
Reference in New Issue
Block a user