Class: Sidetree::Key

Inherits:
Object
  • Object
show all
Defined in:
lib/sidetree/key.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(private_key: nil, public_key: nil, id: nil, purposes: [], type: Sidetree::Params::DEFAULT_PUBKEY_TYPE) ⇒ Key

Returns a new instance of Key.

Parameters:

  • private_key (Integer) (defaults to: nil)

    private key.

  • public_key (ECDSA::Point) (defaults to: nil)

    public key

  • id (String) (defaults to: nil)
  • purposes (Array[String]) (defaults to: [])
  • type (String) (defaults to: Sidetree::Params::DEFAULT_PUBKEY_TYPE)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/sidetree/key.rb', line 10

def initialize(
  private_key: nil,
  public_key: nil,
  id: nil,
  purposes: [],
  type: Sidetree::Params::DEFAULT_PUBKEY_TYPE
)
  if private_key
    unless Key.valid_private_key?(private_key)
      raise Error, "private key is invalid range."
    end

    @private_key = private_key
    pub = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_key)
    if public_key
      unless pub == public_key
        raise Error, "Public and private keys do not match."
      end
    else
      public_key = pub
    end
  end

  unless public_key
    raise Error, "Specify either the private key or the public key"
  end
  unless public_key.is_a?(ECDSA::Point)
    raise Error, "public key must be an ECDSA::Point instance."
  end
  unless ECDSA::Group::Secp256k1.valid_public_key?(public_key)
    raise Error, "public key is invalid."
  end

  @public_key = public_key

  purposes.each do |purpose|
    if purpose && !Sidetree::OP::PublicKeyPurpose.values.include?(purpose)
      raise Error, "Unknown purpose '#{purpose}' specified."
    end
  end

  Sidetree::Validator.validate_id!(id) if id

  @purposes = purposes
  @id = id
  @type = type
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id.



3
4
5
# File 'lib/sidetree/key.rb', line 3

def id
  @id
end

#private_keyObject (readonly)

Returns the value of attribute private_key.



3
4
5
# File 'lib/sidetree/key.rb', line 3

def private_key
  @private_key
end

#public_keyObject (readonly)

Returns the value of attribute public_key.



3
4
5
# File 'lib/sidetree/key.rb', line 3

def public_key
  @public_key
end

#purposesObject (readonly)

Returns the value of attribute purposes.



3
4
5
# File 'lib/sidetree/key.rb', line 3

def purposes
  @purposes
end

#typeObject (readonly)

Returns the value of attribute type.



3
4
5
# File 'lib/sidetree/key.rb', line 3

def type
  @type
end

Class Method Details

.from_jwk(data) ⇒ Sidetree::Key

Generate key instance from jwk Hash.

Parameters:

  • data (Hash)

    jwk Hash object.

Returns:

Raises:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/sidetree/key.rb', line 78

def self.from_jwk(data)
  key_data = data["publicKeyJwk"] ? data["publicKeyJwk"] : data
  key_type = key_data["kty"]
  curve = key_data["crv"]
  if key_type.nil? || key_type != "EC"
    raise Error, "Unsupported key type '#{key_type}' specified."
  end
  if curve.nil? || curve != "secp256k1"
    raise Error, "Unsupported curve '#{curve}' specified."
  end
  raise Error, "x property required." unless key_data["x"]
  raise Error, "y property required." unless key_data["y"]

  # `x` and `y` need 43 Base64URL encoded bytes to contain 256 bits.
  unless key_data["x"].length == 43
    raise Error, "Secp256k1 JWK 'x' property must be 43 bytes."
  end
  unless key_data["y"].length == 43
    raise Error, "Secp256k1 JWK 'y' property must be 43 bytes."
  end

  x = Base64.urlsafe_decode64(key_data["x"])
  y = Base64.urlsafe_decode64(key_data["y"])
  point =
    ECDSA::Format::PointOctetString.decode(
      ["04"].pack("H*") + x + y,
      ECDSA::Group::Secp256k1
    )
  private_key =
    if key_data["d"]
      Base64.urlsafe_decode64(key_data["d"]).unpack1("H*").to_i(16)
    else
      nil
    end

  purposes = data["purposes"] ? data["purposes"] : []
  Key.new(
    public_key: point,
    private_key: private_key,
    purposes: purposes,
    id: data["id"],
    type: data["type"]
  )
end

.generate(id: nil, purposes: [], type: Sidetree::Params::DEFAULT_PUBKEY_TYPE) ⇒ Sidetree::Key

Generate Secp256k1 key.

Parameters:

  • id (String) (defaults to: nil)

    Public key ID.

  • purpose (String)

    Purpose for public key. Supported values defined by [Sidetree::PublicKeyPurpose].

  • type (String) (defaults to: Sidetree::Params::DEFAULT_PUBKEY_TYPE)

    The type of public key defined by w3c-ccg.github.io/ld-cryptosuite-registry/.

Returns:

Raises:



64
65
66
67
68
69
70
71
72
# File 'lib/sidetree/key.rb', line 64

def self.generate(
  id: nil,
  purposes: [],
  type: Sidetree::Params::DEFAULT_PUBKEY_TYPE
)
  private_key =
    1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1)
  Key.new(private_key: private_key, purposes: purposes, id: id, type: type)
end

.valid_private_key?(private_key) ⇒ Boolean

Check whether private is valid range.

Parameters:

  • private_key (Integer)

Returns:

  • (Boolean)


126
127
128
# File 'lib/sidetree/key.rb', line 126

def self.valid_private_key?(private_key)
  0x01 <= private_key && private_key < ECDSA::Group::Secp256k1.order
end

Instance Method Details

#encoded_private_keyString

Return base64 encoded private key.

Returns:

  • (String)


188
189
190
191
192
193
194
195
196
197
# File 'lib/sidetree/key.rb', line 188

def encoded_private_key
  if private_key
    Base64.urlsafe_encode64(
      [private_key.to_s(16).rjust(32 * 2, "0")].pack("H*"),
      padding: false
    )
  else
    nil
  end
end

#jws_sign_keyOpenSSL::PKey::EC

Convert the private key to the format (OpenSSL::PKey::EC) in which it will be signed in JWS.

Returns:

  • (OpenSSL::PKey::EC)


161
162
163
164
# File 'lib/sidetree/key.rb', line 161

def jws_sign_key
  return nil unless private_key
  to_jwk(include_privkey: true).to_key
end

#to_commitmentString

Generate commitment for this key.

Returns:

  • (String)

    Base64 encoded commitment.



168
169
170
171
# File 'lib/sidetree/key.rb', line 168

def to_commitment
  digest = Digest::SHA256.digest(to_jwk.normalize.to_json_c14n)
  Sidetree.to_hash(digest)
end

#to_hObject



179
180
181
182
183
184
# File 'lib/sidetree/key.rb', line 179

def to_h
  h = { publicKeyJwk: to_jwk.normalize, purposes: purposes }
  h[:id] = id if id
  h[:type] = type if type
  h.stringify_keys
end

#to_jwk(include_privkey: false) ⇒ JSON::JWK

Generate JSON::JWK object.

Parameters:

  • include_privkey (Boolean) (defaults to: false)

    whether include private key or not.

Returns:

  • (JSON::JWK)


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/sidetree/key.rb', line 133

def to_jwk(include_privkey: false)
  jwk =
    Sidetree::Util::JWK.parse(
      kty: "EC",
      crv: "secp256k1",
      x:
        Base64.urlsafe_encode64(
          ECDSA::Format::FieldElementOctetString.encode(
            public_key.x,
            public_key.group.field
          ),
          padding: false
        ),
      y:
        Base64.urlsafe_encode64(
          ECDSA::Format::FieldElementOctetString.encode(
            public_key.y,
            public_key.group.field
          ),
          padding: false
        )
    )
  jwk["d"] = encoded_private_key if include_privkey && private_key
  jwk
end

#to_reveal_valueString

Generate reveal value for this key.

Returns:

  • (String)

    Base64 encoded reveal value.



175
176
177
# File 'lib/sidetree/key.rb', line 175

def to_reveal_value
  Sidetree.to_hash(to_jwk.normalize.to_json_c14n)
end