-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: constant time overflow checking while computing offsets (#29)
- Loading branch information
1 parent
f0a851d
commit 637b2bf
Showing
3 changed files
with
92 additions
and
7 deletions.
There are no files selected for viewing
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,39 @@ | ||
package safemath | ||
|
||
import "math/bits" | ||
|
||
// Takes a uint64 and an int16 and outputs their addition as well | ||
// as the ocurrence of an overflow or underflow. | ||
// | ||
// This is a constant-time version of the following function: | ||
// | ||
// func SafeOffset(x uint64, y int16) (res uint64, isOverflow bool) { | ||
// res = x + uint64(y) | ||
// if y < 0 { | ||
// isOverflow = res >= x | ||
// } else { | ||
// isOverflow = res < x | ||
// } | ||
// return | ||
// } | ||
// | ||
// This shows better results because the final bytecode | ||
// doesn't contain any conditional jump instructions | ||
// making it easier for a processor to pipeline the function. | ||
func SafeOffset(x uint64, y int16) (res uint64, isOverflow bool) { | ||
enlargedY := uint64(y) | ||
// I'll leave proving that this is correct as an exercise for the reader :) | ||
res = x + enlargedY | ||
// Why does this work? | ||
// Let's proceed by cases on the most significant bit of (MSB(x)). | ||
// If MSB(x) == 1 and enlargedY < 0 (MSB(enlargedY) == 1) then overflow doesn't happen. | ||
// Let's consider the case enlargedY >= 0 (MSB(enlargedY) == 0). | ||
// In that case we can only wrap up by going to the begining of uint64 range making the MSB(res) = 0. | ||
// This is the second disjunct of the disjunctive formula. | ||
// | ||
// In the same fashion the case MSB(x) == 0 and MSB(enlargedY) == 1 is reasoned about. | ||
// | ||
// Finally, we boil everything down to MSBs by rotating and anding with ...000001. | ||
isOverflow = bits.RotateLeft64((^x&enlargedY&res)|(x & ^enlargedY & ^res), 1)&0x1 != 0 | ||
return | ||
} |
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,35 @@ | ||
package safemath | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestOffsetNeg(t *testing.T) { | ||
res, isOverflow := SafeOffset(1215, -3) | ||
assert.Equal(t, uint64(1212), res) | ||
assert.False(t, isOverflow) | ||
} | ||
|
||
func TestOffsetPos(t *testing.T) { | ||
res, isOverflow := SafeOffset(7, 11) | ||
assert.Equal(t, uint64(18), res) | ||
assert.False(t, isOverflow) | ||
} | ||
|
||
func TestOffsetLeftOverflow(t *testing.T) { | ||
_, isOverflow := SafeOffset(4, -10) | ||
assert.True(t, isOverflow) | ||
} | ||
|
||
func TestOffsetRightOverflow(t *testing.T) { | ||
_, isOverflow := SafeOffset(^uint64(0), 1) | ||
assert.True(t, isOverflow) | ||
} | ||
|
||
func TestOffsetRightNoOverflow(t *testing.T) { | ||
res, isOverflow := SafeOffset(^uint64(0), -12) | ||
assert.Equal(t, uint64(18446744073709551603), res) | ||
assert.False(t, isOverflow) | ||
} |
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