|
| 1 | +// Copyright 2025 The Cockroach Authors. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| 12 | +// implied. See the License for the specific language governing |
| 13 | +// permissions and limitations under the License. |
| 14 | + |
| 15 | +// Package crrand implements functionality related to pseudorandom number |
| 16 | +// generation. |
| 17 | +package crrand |
| 18 | + |
| 19 | +import "math/bits" |
| 20 | + |
| 21 | +// MakePerm64 constructs a new Perm64 from a 64-bit seed, providing a |
| 22 | +// deterministic, pseudorandom, bijective mapping of 64-bit values X to 64-bit |
| 23 | +// values Y. |
| 24 | +func MakePerm64(seed uint64) Perm64 { |
| 25 | + // derive 4 x 32-bit round keys from the 64-bit seed using only ARX ops. |
| 26 | + const c0 = 0x9E3779B97F4A7C15 // golden ratio (used here as XOR salt) |
| 27 | + const c1 = 0xC2B2AE3D27D4EB4F // a constant |
| 28 | + |
| 29 | + var m Perm64 |
| 30 | + s0 := seed |
| 31 | + s1 := bits.RotateLeft64(seed^c0, 13) |
| 32 | + s2 := bits.RotateLeft64(seed^c1, 37) |
| 33 | + s3 := bits.RotateLeft64(seed^c0^c1, 53) |
| 34 | + |
| 35 | + m.seed[0] = uint32(s0) |
| 36 | + m.seed[1] = uint32(s1 >> 32) |
| 37 | + m.seed[2] = uint32(s2) |
| 38 | + m.seed[3] = uint32(s3 >> 32) |
| 39 | + return m |
| 40 | +} |
| 41 | + |
| 42 | +// A Perm64 provides a deterministic, pseudorandom permutation of 64-bit values. |
| 43 | +type Perm64 struct { |
| 44 | + seed [4]uint32 |
| 45 | +} |
| 46 | + |
| 47 | +// At returns the nth value in the permutation of the 64-bit values. The return |
| 48 | +// value may be passed to Index to recover n. The permutation is pseudorandom. |
| 49 | +func (p Perm64) At(n uint64) uint64 { |
| 50 | + // Use a simple Feistel network with 4 rounds to shuffle data. |
| 51 | + L := uint32(n >> 32) |
| 52 | + R := uint32(n) |
| 53 | + for r := range p.seed { |
| 54 | + t := arx(R^p.seed[r], p.seed[(r+1)&3]) |
| 55 | + L, R = R, L^t |
| 56 | + } |
| 57 | + return (uint64(L) << 32) | uint64(R) |
| 58 | +} |
| 59 | + |
| 60 | +// IndexOf inverts the permutation, returning the index of the provided value in |
| 61 | +// the permutation. If y was produced by At(x), then IndexOf(y) returns x. |
| 62 | +func (p Perm64) IndexOf(y uint64) uint64 { |
| 63 | + L := uint32(y >> 32) |
| 64 | + R := uint32(y) |
| 65 | + for r := 3; r >= 0; r-- { |
| 66 | + // reverse of: L, R = R, L ^ arx(R^k[r], k[(r+1)&3]) |
| 67 | + prevR := L |
| 68 | + prevL := R ^ arx(prevR^p.seed[r], p.seed[(r+1)&3]) |
| 69 | + L, R = prevL, prevR |
| 70 | + } |
| 71 | + return (uint64(L) << 32) | uint64(R) |
| 72 | +} |
| 73 | + |
| 74 | +// ARX-only round function. |
| 75 | +func arx(x, k uint32) uint32 { |
| 76 | + x ^= k |
| 77 | + x += bits.RotateLeft32(x, 5) |
| 78 | + x ^= bits.RotateLeft32(x, 7) |
| 79 | + x += bits.RotateLeft32(x, 16) |
| 80 | + return x |
| 81 | +} |
0 commit comments