diff --git a/internal/staging/primitives/uint256/uint256.go b/internal/staging/primitives/uint256/uint256.go index f4348dfa..dfc7cc39 100644 --- a/internal/staging/primitives/uint256/uint256.go +++ b/internal/staging/primitives/uint256/uint256.go @@ -24,12 +24,12 @@ var ( // // It currently implements the primary arithmetic operations (addition, // subtraction, multiplication, squaring, division, negation), bitwise -// operations (lsh, rsh, not), comparison operations (equals, less, greater, +// operations (lsh, rsh, not, or), comparison operations (equals, less, greater, // cmp), interpreting and producing big and little endian bytes, and other // convenience methods such as whether or not the value can be represented as a // uint64 without loss of precision. // -// Future commits will implement bitwise operations (or, and, xor), and other +// Future commits will implement bitwise operations (and, xor), and other // convenience methods such as determining the minimum number of bits required // to represent the current value and text formatting with base conversion. type Uint256 struct { @@ -1287,3 +1287,16 @@ func (n *Uint256) Not() *Uint256 { n.n[3] = ^n.n[3] return n } + +// Or computes the bitwise or of the uint256 and the passed uint256 and stores +// the result in n. +// +// The uint256 is returned to support chaining. This enables syntax like: +// n.Or(n2).AddUint64(1) so that n = (n | n2) + 1. +func (n *Uint256) Or(n2 *Uint256) *Uint256 { + n.n[0] |= n2.n[0] + n.n[1] |= n2.n[1] + n.n[2] |= n2.n[2] + n.n[3] |= n2.n[3] + return n +} diff --git a/internal/staging/primitives/uint256/uint256_test.go b/internal/staging/primitives/uint256/uint256_test.go index 8e2116a5..3f952728 100644 --- a/internal/staging/primitives/uint256/uint256_test.go +++ b/internal/staging/primitives/uint256/uint256_test.go @@ -3038,3 +3038,97 @@ func TestUint256NotRandom(t *testing.T) { } } } + +// TestUint256Or ensures that computing the bitwise or of two uint256s works +// as expected for edge cases. +func TestUint256Or(t *testing.T) { + t.Parallel() + + tests := []struct { + name string // test description + n1 string // first hex encoded test value + n2 string // second hex encoded test value + want string // expected hex encoded result + }{{ + name: "0 | 0", + n1: "0", + n2: "0", + want: "0", + }, { + name: "2^256-1 | 0", + n1: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + n2: "0", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "0 | 2^256-1", + n1: "0", + n2: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "2^256-1 | 2^256-1", + n1: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + n2: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "alternating bits", + n1: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + n2: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "alternating bits 2", + n1: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + n2: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }} + + for _, test := range tests { + // Parse test hex. + n1 := hexToUint256(test.n1) + n2 := hexToUint256(test.n2) + want := hexToUint256(test.want) + + // Ensure bitwise or of the two values produces the expected result. + got := new(Uint256).Set(n1).Or(n2) + if !got.Eq(want) { + t.Errorf("%q: unexpected result -- got: %x, want: %x", test.name, + got, want) + continue + } + } +} + +// TestUint256OrRandom ensures that computing the bitwise or of uint256s created +// from random values works as expected by also performing the same operation +// with big ints and comparing the results. +func TestUint256OrRandom(t *testing.T) { + t.Parallel() + + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().Unix() + rng := rand.New(rand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + for i := 0; i < 100; i++ { + // Generate big integer and uint256 pairs. + bigN1, n1 := randBigIntAndUint256(t, rng) + bigN2, n2 := randBigIntAndUint256(t, rng) + + // Calculate the bitwise or of the values using big ints. + bigIntResult := new(big.Int).Or(bigN1, bigN2) + + // Calculate the bitwise or of the values using uint256s. + uint256Result := new(Uint256).Set(n1).Or(n2) + + // Ensure they match. + bigIntResultHex := fmt.Sprintf("%064x", bigIntResult.Bytes()) + uint256ResultHex := fmt.Sprintf("%064x", uint256Result.Bytes()) + if bigIntResultHex != uint256ResultHex { + t.Fatalf("mismatched or n: %x -- got %x, want %x", n1, bigIntResult, + uint256Result) + } + } +}