Class: Tapyrus::Key

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

Overview

tapyrus key class

Constant Summary collapse

PUBLIC_KEY_SIZE =
65
COMPRESSED_PUBLIC_KEY_SIZE =
33
SIGNATURE_SIZE =
72
COMPACT_SIGNATURE_SIZE =
65
SIG_ALGO =
%i[ecdsa schnorr]
TYPES =
{ uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12 }
MIN_PRIV_KEY_MOD_ORDER =
0x01
MAX_PRIV_KEY_MOD_ORDER =

Order of secp256k1’s generator minus 1.

ECDSA::Group::Secp256k1.order - 1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false) ⇒ Tapyrus::Key

initialize private key

Parameters:

  • priv_key (String) (defaults to: nil)

    a private key with hex format.

  • pubkey (String) (defaults to: nil)

    a public key with hex format.

  • key_type (Integer) (defaults to: nil)

    a key type which determine address type.

  • compressed (Boolean) (defaults to: true)
    Deprecated

    whether public key is compressed.

Raises:

  • (ArgumentError)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/tapyrus/key.rb', line 32

def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
  if key_type.nil? && !compressed.nil? && pubkey.nil?
    warn("Use key_type parameter instead of compressed. compressed parameter removed in the future.")
  end
  if key_type
    @key_type = key_type
    compressed = @key_type != TYPES[:uncompressed]
  else
    @key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
  end
  @secp256k1_module = Tapyrus.secp_impl
  @priv_key = priv_key

  if @priv_key
    raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
    raise ArgumentError, Errors::Messages::INVALID_PRIV_LENGTH unless @priv_key.htb.bytesize == 32
  end
  if pubkey
    @pubkey = pubkey
  else
    @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
  end
  raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
end

Instance Attribute Details

#key_typeObject

Returns the value of attribute key_type.



16
17
18
# File 'lib/tapyrus/key.rb', line 16

def key_type
  @key_type
end

#priv_keyObject

Returns the value of attribute priv_key.



14
15
16
# File 'lib/tapyrus/key.rb', line 14

def priv_key
  @priv_key
end

#pubkeyObject

Returns the value of attribute pubkey.



15
16
17
# File 'lib/tapyrus/key.rb', line 15

def pubkey
  @pubkey
end

#secp256k1_moduleObject (readonly)

Returns the value of attribute secp256k1_module.



17
18
19
# File 'lib/tapyrus/key.rb', line 17

def secp256k1_module
  @secp256k1_module
end

Class Method Details

.compress_or_uncompress_pubkey?(pubkey) ⇒ Boolean

check pubkey (hex) is compress or uncompress pubkey.

Returns:

  • (Boolean)


154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/tapyrus/key.rb', line 154

def self.compress_or_uncompress_pubkey?(pubkey)
  p = pubkey.htb
  return false if p.bytesize < COMPRESSED_PUBLIC_KEY_SIZE
  case p[0]
  when "\x04"
    return false unless p.bytesize == PUBLIC_KEY_SIZE
  when "\x02", "\x03"
    return false unless p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE
  else
    return false
  end
  true
end

.compress_pubkey?(pubkey) ⇒ Boolean

check pubkey (hex) is compress pubkey.

Returns:

  • (Boolean)


169
170
171
172
# File 'lib/tapyrus/key.rb', line 169

def self.compress_pubkey?(pubkey)
  p = pubkey.htb
  p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE && ["\x02", "\x03"].include?(p[0])
end

.from_wif(wif) ⇒ Object

import private key from wif format en.bitcoin.it/wiki/Wallet_import_format

Raises:

  • (ArgumentError)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/tapyrus/key.rb', line 65

def self.from_wif(wif)
  hex = Base58.decode(wif)
  raise ArgumentError, "data is too short" if hex.htb.bytesize < 4
  version = hex[0..1]
  data = hex[2...-8].htb
  checksum = hex[-8..-1]
  raise ArgumentError, "invalid version" unless version == Tapyrus.chain_params.privkey_version
  unless Tapyrus.calc_checksum(version + data.bth) == checksum
    raise ArgumentError, Errors::Messages::INVALID_CHECKSUM
  end
  key_len = data.bytesize
  if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack("C").first == 1
    key_type = TYPES[:compressed]
    data = data[0..-2]
  elsif key_len == 32
    key_type = TYPES[:uncompressed]
  else
    raise ArgumentError, "Wrong number of bytes for a private key, not 32 or 33"
  end
  new(priv_key: data.bth, key_type: key_type)
end

.generate(key_type = TYPES[:compressed]) ⇒ Object

generate key pair



58
59
60
61
# File 'lib/tapyrus/key.rb', line 58

def self.generate(key_type = TYPES[:compressed])
  priv_key, pubkey = Tapyrus.secp_impl.generate_key_pair(compressed: key_type != TYPES[:uncompressed])
  new(priv_key: priv_key, pubkey: pubkey, key_type: key_type)
end

.low_signature?(sig) ⇒ Boolean

check sig is low.

Returns:

  • (Boolean)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/tapyrus/key.rb', line 175

def self.low_signature?(sig)
  s = sig.unpack("C*")
  len_r = s[3]
  len_s = s[5 + len_r]
  val_s = s.slice(6 + len_r, len_s)

  # prettier-ignore
  max_mod_half_order = [
    0x7f,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0xff,
    0x5d,
    0x57,
    0x6e,
    0x73,
    0x57,
    0xa4,
    0x50,
    0x1d,
    0xdf,
    0xe9,
    0x2f,
    0x46,
    0x68,
    0x1b,
    0x20,
    0xa0
  ]
  compare_big_endian(val_s, [0]) > 0 && compare_big_endian(val_s, max_mod_half_order) <= 0
end

.valid_signature_encoding?(sig, data_sig = false) ⇒ Boolean

check sig is correct der encoding. This function is consensus-critical since BIP66.

Parameters:

  • sig (String)

    a signature data with binary format.

  • data_sig (Boolean) (defaults to: false)

    whether data signature or not.

Returns:

  • (Boolean)

    result



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/tapyrus/key.rb', line 224

def self.valid_signature_encoding?(sig, data_sig = false)
  num_parts = data_sig ? 8 : 9
  size = data_sig ? 72 : 73

  return false if sig.bytesize < num_parts || sig.bytesize > size # Minimum and maximum size check

  s = sig.unpack("C*")

  return false if s[0] != 0x30 || s[1] != s.size - (data_sig ? 2 : 3) # A signature is of type 0x30 (compound). Make sure the length covers the entire signature.

  len_r = s[3]
  return false if 5 + len_r >= s.size # Make sure the length of the S element is still inside the signature.

  len_s = s[5 + len_r]
  return false unless len_r + len_s + (data_sig ? 6 : 7) == s.size #Verify that the length of the signature matches the sum of the length of the elements.

  return false unless s[2] == 0x02 # Check whether the R element is an integer.

  return false if len_r == 0 # Zero-length integers are not allowed for R.

  return false unless s[4] & 0x80 == 0 # Negative numbers are not allowed for R.

  # Null bytes at the start of R are not allowed, unless R would otherwise be interpreted as a negative number.
  return false if len_r > 1 && (s[4] == 0x00) && (s[5] & 0x80 == 0)

  return false unless s[len_r + 4] == 0x02 # Check whether the S element is an integer.

  return false if len_s == 0 # Zero-length integers are not allowed for S.
  return false unless (s[len_r + 6] & 0x80) == 0 # Negative numbers are not allowed for S.

  # Null bytes at the start of S are not allowed, unless S would otherwise be interpreted as a negative number.
  return false if len_s > 1 && (s[len_r + 6] == 0x00) && (s[len_r + 7] & 0x80 == 0)

  true
end

Instance Method Details

#compressed?Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/tapyrus/key.rb', line 141

def compressed?
  key_type != TYPES[:uncompressed]
end

#fully_valid_pubkey?(allow_hybrid = false) ⇒ Boolean

fully validate whether this is a valid public key (more expensive than IsValid())

Returns:

  • (Boolean)


261
262
263
# File 'lib/tapyrus/key.rb', line 261

def fully_valid_pubkey?(allow_hybrid = false)
  valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
end

#hash160Object

get hash160 public key.



131
132
133
# File 'lib/tapyrus/key.rb', line 131

def hash160
  Tapyrus.hash160(pubkey)
end

#sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa) ⇒ String

sign data with private key

Parameters:

  • data (String)

    a data to be signed with binary format

  • low_r (Boolean) (defaults to: true)

    flag to apply low-R.

  • extra_entropy (String) (defaults to: nil)

    the extra entropy for rfc6979.

  • algo (Symbol) (defaults to: :ecdsa)

    Algorithms used for verification. Either :ecdsa or :schnorr is supported. default value is :ecdsa.

Returns:

  • (String)

    signature data with binary format

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
# File 'lib/tapyrus/key.rb', line 102

def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
  raise ArgumentError, "Unsupported algorithm has been specified." unless SIG_ALGO.include?(algo)
  case algo
  when :ecdsa
    sign_ecdsa(data, low_r, extra_entropy)
  when :schnorr
    secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: algo)
  else
    false
  end
end

#to_p2pkhObject

Deprecated.

get pay to pubkey hash address



137
138
139
# File 'lib/tapyrus/key.rb', line 137

def to_p2pkh
  Tapyrus::Script.to_p2pkh(hash160).addresses.first
end

#to_pointECDSA::Point

generate pubkey ec point

Returns:



147
148
149
150
151
# File 'lib/tapyrus/key.rb', line 147

def to_point
  p = pubkey
  p ||= generate_pubkey(priv_key, compressed: compressed)
  ECDSA::Format::PointOctetString.decode(p.htb, Tapyrus::Secp256k1::GROUP)
end

#to_wifObject

export private key with wif format



88
89
90
91
92
93
94
# File 'lib/tapyrus/key.rb', line 88

def to_wif
  version = Tapyrus.chain_params.privkey_version
  hex = version + priv_key
  hex += "01" if compressed?
  hex += Tapyrus.calc_checksum(hex)
  Base58.encode(hex)
end

#verify(sig, origin, algo: :ecdsa) ⇒ Boolean

verify signature using public key

Parameters:

  • sig (String)

    signature data with binary format

  • origin (String)

    original message

  • algo (Symbol) (defaults to: :ecdsa)

    Algorithms used for verification. Either :ecdsa or :schnorr is supported. default value is :ecdsa.

Returns:

  • (Boolean)

    verify result



119
120
121
122
123
124
125
126
127
128
# File 'lib/tapyrus/key.rb', line 119

def verify(sig, origin, algo: :ecdsa)
  return false unless valid_pubkey?
  begin
    raise ArgumentError, "Unsupported algorithm has been specified." unless SIG_ALGO.include?(algo)
    sig = ecdsa_signature_parse_der_lax(sig) if algo == :ecdsa
    secp256k1_module.verify_sig(origin, sig, pubkey, algo: algo)
  rescue Exception
    false
  end
end