github.com/cloudflare/circl@v1.5.0/pke/kyber/internal/common/sample.go (about)

     1  package common
     2  
     3  import (
     4  	"encoding/binary"
     5  
     6  	"github.com/cloudflare/circl/internal/sha3"
     7  	"github.com/cloudflare/circl/simd/keccakf1600"
     8  )
     9  
    10  // DeriveX4Available indicates whether the system supports the quick fourway
    11  // sampling variants like PolyDeriveUniformX4.
    12  var DeriveX4Available = keccakf1600.IsEnabledX4()
    13  
    14  // Samples p from a centered binomial distribution with given η.
    15  //
    16  // Essentially CBD_η(PRF(seed, nonce)) from the specification.
    17  func (p *Poly) DeriveNoise(seed []byte, nonce uint8, eta int) {
    18  	switch eta {
    19  	case 2:
    20  		p.DeriveNoise2(seed, nonce)
    21  	case 3:
    22  		p.DeriveNoise3(seed, nonce)
    23  	default:
    24  		panic("unsupported eta")
    25  	}
    26  }
    27  
    28  // Sample p from a centered binomial distribution with n=6 and p=½ - that is:
    29  // coefficients are in {-3, -2, -1, 0, 1, 2, 3} with probabilities {1/64, 3/32,
    30  // 15/64, 5/16, 16/64, 3/32, 1/64}.
    31  func (p *Poly) DeriveNoise3(seed []byte, nonce uint8) {
    32  	keySuffix := [1]byte{nonce}
    33  	h := sha3.NewShake256()
    34  	_, _ = h.Write(seed[:])
    35  	_, _ = h.Write(keySuffix[:])
    36  
    37  	// The distribution at hand is exactly the same as that
    38  	// of (a₁ + a₂ + a₃) - (b₁ + b₂+b₃) where a_i,b_i~U(1).  Thus we need
    39  	// 6 bits per coefficients, thus 192 bytes of input entropy.
    40  
    41  	// We add two extra zero bytes in the buffer to be able to read 8 bytes
    42  	// at the same time (while using only 6.)
    43  	var buf [192 + 2]byte
    44  	_, _ = h.Read(buf[:192])
    45  
    46  	for i := 0; i < 32; i++ {
    47  		// t is interpreted as a₁ + 2a₂ + 4a₃ + 8b₁ + 16b₂ + ….
    48  		t := binary.LittleEndian.Uint64(buf[6*i:])
    49  
    50  		d := t & 0x249249249249        // a₁ + 8b₁ + …
    51  		d += (t >> 1) & 0x249249249249 // a₁ + a₂ + 8(b₁ + b₂) + …
    52  		d += (t >> 2) & 0x249249249249 // a₁ + a₂ + a₃ + 4(b₁ + b₂ + b₃) + …
    53  
    54  		for j := 0; j < 8; j++ {
    55  			a := int16(d) & 0x7 // a₁ + a₂ + a₃
    56  			d >>= 3
    57  			b := int16(d) & 0x7 // b₁ + b₂ + b₃
    58  			d >>= 3
    59  			p[8*i+j] = a - b
    60  		}
    61  	}
    62  }
    63  
    64  // Sample p from a centered binomial distribution with n=4 and p=½ - that is:
    65  // coefficients are in {-2, -1, 0, 1, 2} with probabilities {1/16, 1/4,
    66  // 3/8, 1/4, 1/16}.
    67  func (p *Poly) DeriveNoise2(seed []byte, nonce uint8) {
    68  	keySuffix := [1]byte{nonce}
    69  	h := sha3.NewShake256()
    70  	_, _ = h.Write(seed[:])
    71  	_, _ = h.Write(keySuffix[:])
    72  
    73  	// The distribution at hand is exactly the same as that
    74  	// of (a + a') - (b + b') where a,a',b,b'~U(1).  Thus we need 4 bits per
    75  	// coefficients, thus 128 bytes of input entropy.
    76  
    77  	var buf [128]byte
    78  	_, _ = h.Read(buf[:])
    79  
    80  	for i := 0; i < 16; i++ {
    81  		// t is interpreted as a + 2a' + 4b + 8b' + ….
    82  		t := binary.LittleEndian.Uint64(buf[8*i:])
    83  
    84  		d := t & 0x5555555555555555        // a + 4b + …
    85  		d += (t >> 1) & 0x5555555555555555 // a+a' + 4(b + b') + …
    86  
    87  		for j := 0; j < 16; j++ {
    88  			a := int16(d) & 0x3
    89  			d >>= 2
    90  			b := int16(d) & 0x3
    91  			d >>= 2
    92  			p[16*i+j] = a - b
    93  		}
    94  	}
    95  }
    96  
    97  // For each i, sample ps[i] uniformly from the given seed for coordinates
    98  // xs[i] and ys[i]. ps[i] may be nil and is ignored in that case.
    99  //
   100  // Can only be called when DeriveX4Available is true.
   101  func PolyDeriveUniformX4(ps [4]*Poly, seed *[32]byte, xs, ys [4]uint8) {
   102  	var perm keccakf1600.StateX4
   103  	state := perm.Initialize(false)
   104  
   105  	// Absorb the seed in the four states
   106  	for i := 0; i < 4; i++ {
   107  		v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)])
   108  		for j := 0; j < 4; j++ {
   109  			state[i*4+j] = v
   110  		}
   111  	}
   112  
   113  	// Absorb the coordinates, the SHAKE128 domain separator (0b1111), the
   114  	// start of the padding (0b…001) and the end of the padding 0b100….
   115  	// Recall that the rate of SHAKE128 is 168; ie. 21 uint64s.
   116  	for j := 0; j < 4; j++ {
   117  		state[4*4+j] = uint64(xs[j]) | (uint64(ys[j]) << 8) | (0x1f << 16)
   118  		state[20*4+j] = 0x80 << 56
   119  	}
   120  
   121  	var idx [4]int // indices into ps
   122  	for j := 0; j < 4; j++ {
   123  		if ps[j] == nil {
   124  			idx[j] = N // mark nil polynomials as completed
   125  		}
   126  	}
   127  
   128  	done := false
   129  	for !done {
   130  		// Applies KeccaK-f[1600] to state to get the next 21 uint64s of each of
   131  		// the four SHAKE128 streams.
   132  		perm.Permute()
   133  
   134  		done = true
   135  
   136  	PolyLoop:
   137  		for j := 0; j < 4; j++ {
   138  			if idx[j] == N {
   139  				continue
   140  			}
   141  			for i := 0; i < 7; i++ {
   142  				var t [16]uint16
   143  
   144  				v1 := state[i*3*4+j]
   145  				v2 := state[(i*3+1)*4+j]
   146  				v3 := state[(i*3+2)*4+j]
   147  
   148  				t[0] = uint16(v1) & 0xfff
   149  				t[1] = uint16(v1>>12) & 0xfff
   150  				t[2] = uint16(v1>>24) & 0xfff
   151  				t[3] = uint16(v1>>36) & 0xfff
   152  				t[4] = uint16(v1>>48) & 0xfff
   153  				t[5] = uint16((v1>>60)|(v2<<4)) & 0xfff
   154  
   155  				t[6] = uint16(v2>>8) & 0xfff
   156  				t[7] = uint16(v2>>20) & 0xfff
   157  				t[8] = uint16(v2>>32) & 0xfff
   158  				t[9] = uint16(v2>>44) & 0xfff
   159  				t[10] = uint16((v2>>56)|(v3<<8)) & 0xfff
   160  
   161  				t[11] = uint16(v3>>4) & 0xfff
   162  				t[12] = uint16(v3>>16) & 0xfff
   163  				t[13] = uint16(v3>>28) & 0xfff
   164  				t[14] = uint16(v3>>40) & 0xfff
   165  				t[15] = uint16(v3>>52) & 0xfff
   166  
   167  				for k := 0; k < 16; k++ {
   168  					if t[k] < uint16(Q) {
   169  						ps[j][idx[j]] = int16(t[k])
   170  						idx[j]++
   171  						if idx[j] == N {
   172  							continue PolyLoop
   173  						}
   174  					}
   175  				}
   176  			}
   177  
   178  			done = false
   179  		}
   180  	}
   181  
   182  	for i := 0; i < 4; i++ {
   183  		if ps[i] != nil {
   184  			ps[i].Tangle()
   185  		}
   186  	}
   187  }
   188  
   189  // Sample p uniformly from the given seed and x and y coordinates.
   190  //
   191  // Coefficients are reduced and will be in "tangled" order.  See Tangle().
   192  func (p *Poly) DeriveUniform(seed *[32]byte, x, y uint8) {
   193  	var seedSuffix [2]byte
   194  	var buf [168]byte // rate of SHAKE-128
   195  
   196  	seedSuffix[0] = x
   197  	seedSuffix[1] = y
   198  
   199  	h := sha3.NewShake128()
   200  	_, _ = h.Write(seed[:])
   201  	_, _ = h.Write(seedSuffix[:])
   202  
   203  	i := 0
   204  	for {
   205  		_, _ = h.Read(buf[:])
   206  
   207  		for j := 0; j < 168; j += 3 {
   208  			t1 := (uint16(buf[j]) | (uint16(buf[j+1]) << 8)) & 0xfff
   209  			t2 := (uint16(buf[j+1]>>4) | (uint16(buf[j+2]) << 4)) & 0xfff
   210  
   211  			if t1 < uint16(Q) {
   212  				p[i] = int16(t1)
   213  				i++
   214  
   215  				if i == N {
   216  					break
   217  				}
   218  			}
   219  
   220  			if t2 < uint16(Q) {
   221  				p[i] = int16(t2)
   222  				i++
   223  
   224  				if i == N {
   225  					break
   226  				}
   227  			}
   228  		}
   229  
   230  		if i == N {
   231  			break
   232  		}
   233  	}
   234  
   235  	p.Tangle()
   236  }