def bmix(self, source, target): """ block mixing function used by smix() uses salsa20/8 core to mix block contents. :arg source: source to read from. should be list of 32*r 4-byte integers (2*r salsa20 blocks). :arg target: target to write to. should be list with same size as source. the existing value of this buffer is ignored. .. warning:: this operates *in place* on target, so source & target should NOT be same list. .. note:: * time cost is ``O(r)`` -- loops 16*r times, salsa20() has ``O(1)`` cost. * memory cost is ``O(1)`` -- salsa20() uses 16 x uint4, all other operations done in-place. """ ## assert source is not target # Y[-1] = B[2r-1], Y[i] = hash( Y[i-1] xor B[i]) # B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ half = self.bmix_half_len # 16*r out of 32*r - start of Y_1 tmp = source[-16:] # 'X' in scrypt source siter = iter(source) j = 0 while j < half: jn = j + 16 target[j:jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter)) target[half + j:half + jn] = tmp = salsa20( a ^ b for a, b in izip(tmp, siter)) j = jn
def _bmix_1(self, source, target): """special bmix() method optimized for ``r=1`` case""" B = source[16:] target[:16] = tmp = salsa20(a ^ b for a, b in izip(B, iter(source))) target[16:] = salsa20(a ^ b for a, b in izip(tmp, B))
def smix(self, input): """run SCrypt smix function on a single input block :arg input: byte string containing input data. interpreted as 32*r little endian 4 byte integers. :returns: byte string containing output data derived by mixing input using n & r parameters. .. note:: time & mem cost are both ``O(n * r)`` """ # gather locals bmix = self.bmix bmix_struct = self.bmix_struct integerify = self.integerify n = self.n # parse input into 32*r integers ('X' in scrypt source) # mem cost -- O(r) buffer = list(bmix_struct.unpack(input)) # starting with initial buffer contents, derive V s.t. # V[0]=initial_buffer ... V[i] = bmix(V[i-1], V[i-1]) ... V[n-1] = bmix(V[n-2], V[n-2]) # final buffer contents should equal bmix(V[n-1], V[n-1]) # # time cost -- O(n * r) -- n loops, bmix is O(r) # mem cost -- O(n * r) -- V is n-element array of r-element tuples # NOTE: could do time / memory tradeoff to shrink size of V def vgen(): i = 0 while i < n: last = tuple(buffer) yield last bmix(last, buffer) i += 1 V = list(vgen()) # generate result from X & V. # # time cost -- O(n * r) -- loops n times, calls bmix() which has O(r) time cost # mem cost -- O(1) -- allocates nothing, calls bmix() which has O(1) mem cost get_v_elem = V.__getitem__ n_mask = n - 1 i = 0 while i < n: j = integerify(buffer) & n_mask result = tuple(a ^ b for a, b in izip(buffer, get_v_elem(j))) bmix(result, buffer) i += 1 # # NOTE: we could easily support arbitrary values of ``n``, not just powers of 2, # # but very few implementations have that ability, so not enabling it for now... # if not n_is_log_2: # while i < n: # j = integerify(buffer) % n # tmp = tuple(a^b for a,b in izip(buffer, get_v_elem(j))) # bmix(tmp,buffer) # i += 1 # repack tmp return bmix_struct.pack(*buffer)