class RingSig::Hasher

A customized hasher specifically for Ring Signatures.

Constants

Secp256k1_Sha256
Secp256r1_Sha256
Secp384r1_Sha384

Attributes

algorithm[R]

@return [#digest]

group[R]

@return [ECDSA::Group]

Public Class Methods

new(group, algorithm) click to toggle source

Creates a new instance of {Hasher}.

@note The byte-length of the group’s order and the digest method must

match, or else signatures generated from this hasher will leak the
position of the true signer.

@param group [ECDSA::Group] @param algorithm [#digest]

# File lib/ring_sig/hasher.rb, line 18
def initialize(group, algorithm)
  @group = group
  @algorithm = algorithm

  algorithm_byte_length = algorithm.digest('a').size
  if group.byte_length != algorithm_byte_length
    raise ArgumentError, "Group's byte length (#{group.byte_length}), does not match hash algorithm's byte length (#{algorithm_byte_length})"
  end

  digest_max = 2 ** (algorithm_byte_length * 8) - 1
  if digest_max < group.order
    raise ArgumentError, "Invalid ECDSA group. Group's order must be less than the hash algorithm's maximum value"
  end

  @hash_cieling = digest_max - digest_max % group.order
end

Public Instance Methods

==(other) click to toggle source

@return [Boolean] true if the hashers are equal.

# File lib/ring_sig/hasher.rb, line 96
def ==(other)
  group == other.group && algorithm == other.algorithm
end
hash_array(array) click to toggle source

Hashes an array. Converts the Array to an OpenSSL::ASN1::Sequence der string, and then hashes that string.

@param array [Array<String,Integer,ECDSA::Point>] The array to be hashed. @return [Integer] A number between 0 and the group’s order.

# File lib/ring_sig/hasher.rb, line 54
def hash_array(array)
  array = array.map do |e|
    case e
    when String
      OpenSSL::ASN1::UTF8String.new(e)
    when Integer
      OpenSSL::ASN1::Integer.new(e)
    when ECDSA::Point
      OpenSSL::ASN1::OctetString.new(ECDSA::Format::PointOctetString.encode(e, compression: true))
    else
      raise ArgumentError, "Unsupported type: #{p.inspect}"
    end
  end

  hash_string(OpenSSL::ASN1::Sequence.new(array).to_der)
end
hash_point(point) click to toggle source

Hashes a point to another point.

@param point [ECDSA::Point] The point to be hashed. @return [ECDSA::Point] A new point, deterministically computed from the

input point.
# File lib/ring_sig/hasher.rb, line 76
def hash_point(point)
  @group.generator * hash_array(point.coords)
end
hash_string(s) click to toggle source

Uniformly hashes a string to a number between 0 and the group’s order.

@param s (String) The string to be hashed. @return (Integer) A number between 0 and the group’s order.

# File lib/ring_sig/hasher.rb, line 39
def hash_string(s)
  n = nil
  loop do
    s = algorithm.digest(s)
    n = s.unpack('H*').first.to_i(16)
    break if n < @hash_cieling
  end
  n % group.order
end
shuffle(array, seed) click to toggle source

Shuffles an array in a deterministic manner.

@param array (Array) The array to be shuffled. @param seed (Integer) A random seed which determines the outcome of the

shuffle.

@return (Array) The shuffled array.

# File lib/ring_sig/hasher.rb, line 86
def shuffle(array, seed)
  seed_array = [seed, 0]
  (array.size - 1).downto(1) do |i|
    r = next_rand(i + 1, seed_array)
    array[i], array[r] = array[r], array[i]
  end
  array
end

Private Instance Methods

next_rand(n, seed_array) click to toggle source

Deterministically returns a random number between 0 and n.

@param n (Integer) The maximum value. @param seed_array (Array<Integer>) A pair ‘[seed, suffix]`.

The suffix will be modified.

@return (Integer) A number between 0 and n.

# File lib/ring_sig/hasher.rb, line 108
def next_rand(n, seed_array)
  loop do
    r = hash_array(seed_array)
    seed_array[1] += 1
    return r % n if r < @group.order - @group.order % n
  end
end