I am implementing EnScrypt for an SQRL client in Go. The function needs to run until it has used a minimum amount of CPU time. My Python code looks like this:
def enscrypt_time(salt, password, seconds, n=9, r=256):
N = 1 << n
start = time.process_time()
end = start + seconds
data = acc = scrypt.hash(password, salt, N, r, 1, 32)
i = 1
while time.process_time() < end:
data = scrypt.hash(password, data, N, r, 1, 32)
acc = xor_bytes(acc, data)
i += 1
return i, time.process_time() - start, acc
Converting this to Go is pretty simple except for the process_time
function.
I can't use time.Time
/ Timer
because those measure wall-clock time (which is affected by everything else that may be running on the system). I need the CPU time actually used, ideally by the function, or at least by the thread or process it is running in.
What is the Go equivalent of process_time
?
https://docs.python.org/3/library/time.html#time.process_time
You may use runtime.LockOSThread()
to wire the calling goroutine to its current OS thread. This will ensure that no other goroutines will be scheduled to this thread, so your goroutine will run and not get interrupted or put on hold. No other goroutines will interfere when thread is locked.
After this, you just need a loop until the given seconds have passed. You must call runtime.UnlockOSThread()
to "release" the thread and make it available for other goroutines for execution, best done as a defer
statement.
See this example:
func runUntil(end time.Time) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
for time.Now().Before(end) {
}
}
To make it wait for 2 seconds, it could look like this:
start := time.Now()
end := start.Add(time.Second * 2)
runUntil(end)
fmt.Println("Verify:", time.Now().Sub(start))
This prints for example:
Verify: 2.0004556s
Of course you can specify less than a second too, e.g. to wait for 100 ms:
start := time.Now()
runUntil(start.Add(time.Millisecond * 100))
fmt.Println("Verify:", time.Now().Sub(start))
Output:
Verify: 100.1278ms
You may use a different version of this function if that suits you better, one that takes the amount of time to "wait" as a value of time.Duration
:
func wait(d time.Duration) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
for end := time.Now().Add(d); time.Now().Before(end); {
}
}
Using this:
start = time.Now()
wait(time.Millisecond * 200)
fmt.Println("Verify:", time.Now().Sub(start))
Output:
Verify: 200.1546ms
Note: Note that the loops in the above functions will use CPU relentlessly as there is no sleep or blocking IO in them, they will just query the current system time and compare it to the deadline.
What if the attacker increases system load by multiple concurrent attempts?
The Go runtime limits the system threads that can simultaneously execute goroutines. This is controlled by runtime.GOMAXPROCS()
, so this is already a limitation. It defaults to the number of available CPU cores, and you can change it anytime. This also poses a bottleneck though, as by using runtime.LockOSThread()
, if the number of locked threads equals to GOMAXPROCS
at any given time, that would block execution of other goroutines until a thread is unlocked.
See related questions:
Number of threads used by Go runtime
Why does it not create many threads when many goroutines are blocked in writing file in golang?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With