Class: SecretSharing::Shamir::Secret

Inherits:
Object
  • Object
show all
Includes:
SecretSharing::Shamir
Defined in:
lib/secretsharing/shamir/secret.rb

Overview

A SecretSharing::Shamir::Secret object represents a Secret in the Shamir secret sharing scheme. Secrets can be passed in as an input argument when creating a new SecretSharing::Shamir::Container or can be the output from a Container that has successfully decoded shares. A new Secret take 0 or 1 args. Zero args means the Secret will be initialized with a random Numeric object with the Secret::DEFAULT_BITLENGTH. If a single argument is passed it can be a String, or Integer. If its a String, its expected to be of a special encoding that was generated as the output of calling #to_s on another Secret object. If the object type is an Integer it can be up to 4096 bits in length.

All secrets are internally represented as a Numeric which can be retrieved in its raw form using #secret.

Constant Summary collapse

MAX_BITLENGTH =

FIXME : Is a MAX_BITLENGTH really needed? Can it be larger if so?

4096

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SecretSharing::Shamir

#evaluate_polynomial_at, #extended_gcd, #get_prime_number, #get_random_number, #get_random_number_with_bitlength, #invmod, #lagrange, #miller_rabin_prime?, #mod_exp

Constructor Details

#initialize(opts = {}) ⇒ Secret

rubocop:disable Metrics/PerceivedComplexity



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/secretsharing/shamir/secret.rb', line 46

def initialize(opts = {})
  opts = {
    :secret => get_random_number(32) # 32 Bytes, 256 Bits
  }.merge!(opts)

  # override with options
  opts.each_key do |k|
    if self.respond_to?("#{k}=")
      send("#{k}=", opts[k])
    else
      fail ArgumentError, "Argument '#{k}' is not allowed"
    end
  end

  # FIXME : Do we really need the ability for a String arg to re-instantiate a Secret?
  # FIXME : If its a String, shouldn't it be able to be an arbitrary String converted to/from a Number?

  if opts[:secret].is_a?(String)
    # Decode a Base64.urlsafe_encode64 String which contains a Base 36 encoded Bignum back into a Bignum
    # See : Secret#to_s for forward encoding method.
    stripped_secret = opts[:secret].strip
    fail ArgumentError, 'invalid secret (empty String)' if stripped_secret.empty?
    decoded_secret = Base64.urlsafe_decode64(stripped_secret)
    fail ArgumentError, 'invalid secret (base64 decode returned nil or empty String)' if decoded_secret.empty?
    int_secret = decoded_secret.to_i(36)
    fail ArgumentError, 'invalid secret (not an Integer)' unless int_secret.is_a?(Integer)
    fail ArgumentError, 'invalid secret (Integer bit length < 100)' unless int_secret.bit_length > 100
    @secret = int_secret
  end

  @secret = opts[:secret] if @secret.nil?
  fail ArgumentError, "Secret must be an Integer, not a '#{@secret.class}'" unless @secret.is_a?(Integer)

  # Get the number of binary bits in this secret's value.
  @bitlength = @secret.bit_length

  fail ArgumentError, "Secret must have a bitlength less than or equal to #{MAX_BITLENGTH}" if @bitlength > MAX_BITLENGTH

  generate_hmac
end

Instance Attribute Details

#bitlengthObject

Returns the value of attribute bitlength.



40
41
42
# File 'lib/secretsharing/shamir/secret.rb', line 40

def bitlength
  @bitlength
end

#hmacObject

Returns the value of attribute hmac.



40
41
42
# File 'lib/secretsharing/shamir/secret.rb', line 40

def hmac
  @hmac
end

#secretObject

Returns the value of attribute secret.



41
42
43
# File 'lib/secretsharing/shamir/secret.rb', line 41

def secret
  @secret
end

Instance Method Details

#==(other) ⇒ Object

Secrets are equal if the Numeric in @secret is the same. Do secure constant-time comparison of the objects.



90
91
92
93
94
# File 'lib/secretsharing/shamir/secret.rb', line 90

def ==(other)
  other_secret_hash = RbNaCl::Hash.blake2b(other.secret.to_s, digest_size: 32)
  own_secret_hash   = RbNaCl::Hash.blake2b(@secret.to_s, digest_size: 32)
  RbNaCl::Util.verify32(other_secret_hash, own_secret_hash)
end

#secret?Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/secretsharing/shamir/secret.rb', line 102

def secret?
  @secret.is_a?(Integer)
end

#to_sObject



106
107
108
109
110
111
# File 'lib/secretsharing/shamir/secret.rb', line 106

def to_s
  # Convert the Bignum to a Base 36 encoded String
  # Wrap the Base 36 encoded String as a URL safe Base 64 encoded String
  # Combined this should result in a relatively compact and portable String
  Base64.urlsafe_encode64(@secret.to_s(36))
end

#valid_hmac?Boolean

See : generate_hmac

Returns:

  • (Boolean)


114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/secretsharing/shamir/secret.rb', line 114

def valid_hmac?
  return false if !@secret.is_a?(Integer) || @hmac.to_s.empty? || @secret.to_s.empty?
  hash = RbNaCl::Hash.sha512(@secret.to_s)
  key = hash[0, 32]
  authenticator = RbNaCl::Util.hex2bin(@hmac)
  msg = hash[33, 64]
  begin
    RbNaCl::HMAC::SHA256.verify(key, authenticator, msg)
  rescue
    false
  end
end