class TSS::Splitter

Warning, you probably don't want to use this directly. Instead see the TSS module.

TSS::Splitter has responsibility for splitting a secret into an Array of String shares.

Constants

C
SHARE_HEADER_STRUCT

Attributes

format[R]
hash_alg[R]
identifier[R]
num_shares[R]
padding[R]
secret[R]
threshold[R]

Public Class Methods

new(opts = {}) click to toggle source
# File lib/tss/splitter.rb, line 15
def initialize(opts = {})
  @secret = opts.fetch(:secret)
  @threshold = opts.fetch(:threshold, 3)
  @num_shares = opts.fetch(:num_shares, 5)
  @identifier = opts.fetch(:identifier, SecureRandom.hex(8))
  @hash_alg = opts.fetch(:hash_alg, 'SHA256')
  @format = opts.fetch(:format, 'HUMAN')
  @padding = opts.fetch(:padding, true)
end

Public Instance Methods

split() click to toggle source
# File lib/tss/splitter.rb, line 61
def split
  num_shares_not_less_than_threshold!(threshold, num_shares)

  # Append needed PKCS#7 padding to the string
  secret_padded = padding ? Util.pad(secret) : secret

  # Calculate the cryptographic hash of the secret string
  secret_hash = Hasher.byte_array(hash_alg, secret)

  # RTSS : Combine the secret with a hash digest before splitting. When
  # recombine the two will be separated again and the hash will be used
  # to validate the correct secret was returned.
  # secret || padding || hash(secret)
  secret_pad_hash_bytes = Util.utf8_to_bytes(secret_padded) + secret_hash

  secret_bytes_is_smaller_than_max_size!(secret_pad_hash_bytes)

  # For each share, a distinct Share Index is generated. Each Share
  # Index is an octet other than the all-zero octet. All of the Share
  # Indexes used during a share generation process MUST be distinct.
  # Each share is initialized to the Share Index associated with that
  # share.
  shares = []
  (1..num_shares).each { |n| shares << [n] }

  # For each octet of the secret, the following steps are performed.
  #
  # An array A of M octets is created, in which the array element A[0]
  # contains the octet of the secret, and the array elements A[1],
  # ..., A[M-1] contain octets that are selected independently and
  # uniformly at random.
  #
  # For each share, the value of f(X,A) is
  # computed, where X is the Share Index of the share, and the
  # resulting octet is appended to the share.
  #
  # After the procedure is done, each share contains one more octet than
  # does the secret.  The share format can be illustrated as
  #
  # +---------+---------+---------+---------+---------+
  # |    X    | f(X,A)  | f(X,B)  | f(X,C)  |   ...   |
  # +---------+---------+---------+---------+---------+
  #
  # where X is the Share Index of the share, and A, B, and C are arrays
  # of M octets; A[0] is equal to the first octet of the secret, B[0] is
  # equal to the second octet of the secret, and so on.
  #
  secret_pad_hash_bytes.each do |byte|
    # Unpack random Byte String into Byte Array of 8 bit unsigned Integers
    r = SecureRandom.random_bytes(threshold - 1).unpack('C*')

    # Build each share one byte at a time for each byte of the secret.
    shares.map! { |s| s << Util.f(s[0], [byte] + r) }
  end

  # build up a common binary struct header for all shares
  header = share_header(identifier, hash_alg, threshold, shares.first.length)

  # create each binary or human share and return it.
  shares.map! do |s|
    binary = (header + s.pack('C*')).force_encoding('ASCII-8BIT')
    # join with URL safe '~'
    human = ['tss', 'v1', identifier, threshold, Base64.urlsafe_encode64(binary)].join('~')
    format == 'BINARY' ? binary : human
  end

  return shares
end

Private Instance Methods

num_shares_not_less_than_threshold!(threshold, num_shares) click to toggle source
# File lib/tss/splitter.rb, line 139
def num_shares_not_less_than_threshold!(threshold, num_shares)
  if num_shares < threshold
    raise TSS::ArgumentError, "invalid num_shares, must be >= threshold (#{threshold})"
  else
    return true
  end
end
secret_bytes_is_smaller_than_max_size!(secret_bytes) click to toggle source
# File lib/tss/splitter.rb, line 154
def secret_bytes_is_smaller_than_max_size!(secret_bytes)
  if secret_bytes.size > TSS::MAX_SECRET_SIZE
    raise TSS::ArgumentError, 'invalid secret, combined padded and hashed secret is too large'
  else
    return true
  end
end
share_header(identifier, hash_alg, threshold, share_len) click to toggle source
# File lib/tss/splitter.rb, line 171
def share_header(identifier, hash_alg, threshold, share_len)
  SHARE_HEADER_STRUCT.encode(identifier: identifier,
                             hash_id: Hasher.code(hash_alg),
                             threshold: threshold,
                             share_len: share_len)
end