// Native Ed25519 timing sampler.
//
// This is a dudect-style engineering probe, not a formal side-channel proof:
// two same-shape input classes are randomly interleaved, timed on the native
// backend, and summarized with Welch's t statistic.
///|
extern "C" fn native_now_ticks() -> Int64 = "moon_evidence_timing_now_ticks"
///|
extern "C" fn native_ticks_per_second() -> Int64 = "moon_evidence_timing_ticks_per_second"
///|
extern "C" fn native_print_env() -> Unit = "moon_evidence_timing_print_native_env"
///|
let default_samples : Int = 10000
///|
let default_warmup : Int = 512
///|
let usage : String =
#|moon-evidence-native-timing
#|
#|Usage:
#| moon-evidence-native-timing [--target verify|sign-message|sign-secret|both]
#| [--samples ] [--warmup ]
#|
#|Targets:
#| verify Ed25519 verify with two valid public-input classes
#| sign-message Ed25519 sign with fixed secret and two public messages
#| sign-secret Ed25519 sign with two secret-key classes and fixed message
#| both Runs verify, sign-message, and sign-secret
///|
struct Options {
target : String
samples : Int
warmup : Int
}
///|
struct Rng {
mut state : UInt64
}
///|
fn Rng::make(seed : UInt64) -> Rng {
{ state: seed }
}
///|
fn Rng::next(self : Rng) -> UInt64 {
self.state = self.state + 0x9E3779B97F4A7C15UL
let mut z = self.state
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL
z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL
z ^ (z >> 31)
}
///|
fn Rng::coin(self : Rng) -> Bool {
(self.next() & 1UL) == 1UL
}
///|
struct OnlineStats {
mut count : Int
mut mean : Double
mut m2 : Double
}
///|
fn OnlineStats::new() -> OnlineStats {
{ count: 0, mean: 0.0, m2: 0.0 }
}
///|
fn OnlineStats::add(self : OnlineStats, x : Double) -> Unit {
self.count = self.count + 1
let delta = x - self.mean
self.mean = self.mean + delta / self.count.to_double()
let delta2 = x - self.mean
self.m2 = self.m2 + delta * delta2
}
///|
fn OnlineStats::variance(self : OnlineStats) -> Double {
if self.count < 2 {
0.0
} else {
self.m2 / (self.count - 1).to_double()
}
}
///|
struct TimingResult {
label : String
class_a : OnlineStats
class_b : OnlineStats
checksum : Int
}
///|
fn parse_positive_int(s : String) -> Int? {
if s.length() == 0 {
return None
}
let mut value = 0
for i in 0.. 57 {
return None
}
value = value * 10 + (c - 48)
}
Some(value)
}
///|
fn parse_options(raw : Array[String]) -> Options? {
let mut target = "both"
let mut samples = default_samples
let mut warmup = default_warmup
let mut i = 0
while i < raw.length() {
let arg = raw[i]
if arg == "--target" && i + 1 < raw.length() {
i = i + 1
target = raw[i]
} else if arg == "--samples" && i + 1 < raw.length() {
i = i + 1
match parse_positive_int(raw[i]) {
Some(n) => samples = n
None => return None
}
} else if arg == "--warmup" && i + 1 < raw.length() {
i = i + 1
match parse_positive_int(raw[i]) {
Some(n) => warmup = n
None => return None
}
} else if arg == "--help" || arg == "-h" || arg == "help" {
println(usage)
return None
}
i = i + 1
}
if samples < 2 || samples % 2 != 0 || warmup < 0 {
return None
}
match target {
"verify" | "sign-message" | "sign-secret" | "both" =>
Some({ target, samples, warmup })
_ => None
}
}
///|
fn byte_range(start : Int, count : Int, step : Int) -> Bytes {
let out : Array[Byte] = []
for i in 0.. Int {
let mut acc = data.length()
if data.length() > 0 {
acc = acc + data[0].to_int()
acc = acc + data[data.length() - 1].to_int()
}
acc
}
///|
fn elapsed_ns(
start_ticks : Int64,
end_ticks : Int64,
ticks_per_second : Int64,
) -> Double {
let delta = end_ticks - start_ticks
delta.to_double() * 1000000000.0 / ticks_per_second.to_double()
}
///|
fn record_verify(
pk : Bytes,
msg : Bytes,
sig : Bytes,
ticks_per_second : Int64,
) -> (Double, Int) {
let start = native_now_ticks()
let valid = @crypto.ed25519_verify(pk, msg, sig)
let end = native_now_ticks()
if !valid {
println("ERROR: valid signature rejected during timing run")
@sys.exit(2)
}
(elapsed_ns(start, end, ticks_per_second), valid.to_int())
}
///|
fn record_sign(
sk : Bytes,
msg : Bytes,
ticks_per_second : Int64,
) -> (Double, Int) {
let start = native_now_ticks()
let sig = @crypto.ed25519_sign(sk, msg)
let end = native_now_ticks()
if sig.length() != 64 {
println("ERROR: signer returned non-64-byte signature")
@sys.exit(2)
}
(elapsed_ns(start, end, ticks_per_second), checksum_bytes(sig))
}
///|
fn run_verify(
samples : Int,
warmup : Int,
rng : Rng,
ticks_per_second : Int64,
) -> TimingResult {
let sk = byte_range(0, 32, 1)
let pk = @crypto.ed25519_public_key(sk)
let msg_a = byte_range(0, 64, 1)
let msg_b = byte_range(255, 64, -1)
let sig_a = @crypto.ed25519_sign(sk, msg_a)
let sig_b = @crypto.ed25519_sign(sk, msg_b)
for i in 0.. TimingResult {
let sk = byte_range(0, 32, 1)
let msg_a = byte_range(17, 64, 3)
let msg_b = byte_range(203, 64, -5)
for i in 0.. TimingResult {
let sk_a = byte_range(0, 32, 1)
let sk_b = byte_range(131, 32, 7)
let msg = byte_range(41, 64, 9)
for i in 0.. Double {
let denom = (a.variance() / a.count.to_double() +
b.variance() / b.count.to_double()).sqrt()
if denom == 0.0 {
0.0
} else {
(a.mean - b.mean) / denom
}
}
///|
fn print_result(result : TimingResult) -> Unit {
let a = result.class_a
let b = result.class_b
let t = welch_t(a, b)
println("target: \{result.label}")
println("samples_total: \{a.count + b.count}")
println("samples_class_a: \{a.count}")
println("samples_class_b: \{b.count}")
println("class_a_mean_ns: \{a.mean}")
println("class_a_variance_ns2: \{a.variance()}")
println("class_b_mean_ns: \{b.mean}")
println("class_b_variance_ns2: \{b.variance()}")
println("welch_t: \{t}")
println("checksum: \{result.checksum}")
if t < -4.5 || t > 4.5 {
println(
"timing_assessment: suspicious |t| >= 4.5; rerun and inspect before making any claim",
)
} else {
println(
"timing_assessment: no obvious timing difference observed in this run (not a proof)",
)
}
}
///|
fn main {
guard parse_options(@env.args()) is Some(opts) else {
println(usage)
@sys.exit(2)
return
}
let ticks_per_second = native_ticks_per_second()
println("moon_evidence_native_timing: Ed25519 dudect-style sampler")
println("samples_requested: \{opts.samples}")
println("warmup_requested: \{opts.warmup}")
println("target_requested: \{opts.target}")
native_print_env()
let rng = Rng::make(0x6d6f6f6e2d74696dUL) // "moon-tim"
match opts.target {
"verify" =>
print_result(run_verify(opts.samples, opts.warmup, rng, ticks_per_second))
"sign-message" =>
print_result(
run_sign_message(opts.samples, opts.warmup, rng, ticks_per_second),
)
"sign-secret" =>
print_result(
run_sign_secret(opts.samples, opts.warmup, rng, ticks_per_second),
)
_ => {
print_result(run_verify(opts.samples, opts.warmup, rng, ticks_per_second))
print_result(
run_sign_message(opts.samples, opts.warmup, rng, ticks_per_second),
)
print_result(
run_sign_secret(opts.samples, opts.warmup, rng, ticks_per_second),
)
}
}
println(
"TIMING NATIVE PASS: functional checks passed; interpret Welch t as local empirical evidence, not a formal side-channel proof",
)
}