Class: Derivator::Key
- Inherits:
-
Object
- Object
- Derivator::Key
- Defined in:
- lib/derivator/key.rb
Overview
SLIP10 key derivation.
Constant Summary collapse
- SECP256K1_DER_PRIVATE_PREFIX =
secp256k1 EC private key binary prefix when DER-encoded
'303e020100301006072a8648ce3d020106052b8104000a042730250201010420'- NIST256P1_DER_PRIVATE_PREFIX =
nist256p1 EC private key binary prefix when DER-encoded
'3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420'- ED25519_DER_PRIVATE_PREFIX =
ed25519 EC private key binary prefix when DER-encoded
'302e020100300506032b657004220420'- ED25519_DER_PUBLIC_PREFIX =
ed25519 EC public key binary prefix when DER-encoded
'302a300506032b65700321'- SECP256K1_SEED_KEY =
secp256k1 BIP32/SLIP10 seed key
'Bitcoin seed'- NIST256P1_SEED_KEY =
nist256p1 SLIP10 seed key
'Nist256p1 seed'- ED25519_SEED_KEY =
ed25519 SLIP10 seed key
'ed25519 seed'- SECP256K1_LARGEST_KEY =
secp256k1 largest valid private key
0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140- NIST256P1_LARGEST_KEY =
nist256p1 largest valid private key
0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551- ED25519_LARGEST_KEY =
ed25519 largest valid private key (unlimited)
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Instance Attribute Summary collapse
-
#chain_code ⇒ String
readonly
Chain code (in binary format).
-
#curve ⇒ Symbol
readonly
EC curve used for the key (
:secp256k1,:nist256p1or:ed25519). -
#private_key ⇒ String
readonly
Private key (in binary format).
Class Method Summary collapse
-
.from_hex(private_key_hex, chain_code_hex, curve = :secp256k1) ⇒ Key
Creates new key from private key and chain code hex strings.
-
.from_seed(seed_hex, curve = :secp256k1) ⇒ Key
Creates new key from seed hex string.
-
.valid_private_key?(private_key_hex, curve = :secp256k1) ⇒ true, false
Checks private key for a particular curve (used internally).
Instance Method Summary collapse
-
#==(other) ⇒ true, false
Compares with another Key.
-
#chain_code_hex ⇒ String
Chain code as hex string.
-
#derive(path) ⇒ Object
Derive child key.
-
#fingerprint ⇒ String
BIP32 fingerprint (first 4 bytes of HASH160 of public key) as hex string.
-
#initialize(private_key, chain_code, curve = :secp256k1) ⇒ Key
constructor
Creates private key from binary data.
-
#openssl_group ⇒ OpenSSL::PKey::EC::Group
Creates OpenSSL::PKey::EC::Group instance for the key’s #curve.
-
#openssl_pkey ⇒ OpenSSL::PKey::EC::Point
Creates OpenSSL::PKey instance.
-
#private_key_hex ⇒ String
Private key as hex string.
-
#public_key ⇒ String
Public key (in binary format).
-
#public_key_hex ⇒ String
Public key as hex string.
-
#to_pem ⇒ String
Exports key to PEM format.
Constructor Details
#initialize(private_key, chain_code, curve = :secp256k1) ⇒ Key
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/derivator/key.rb', line 111 def initialize(private_key, chain_code, curve = :secp256k1) @private_key = private_key.dup.freeze @chain_code = chain_code.dup.freeze unless %i[secp256k1 nist256p1 ed25519].include?(curve) raise ArgumentError.new('curve must be :secp256k1, :nist256p1 or :ed25519') end @curve = curve end |
Instance Attribute Details
#chain_code ⇒ String (readonly)
Returns chain code (in binary format).
36 37 38 |
# File 'lib/derivator/key.rb', line 36 def chain_code @chain_code end |
#curve ⇒ Symbol (readonly)
Returns EC curve used for the key (:secp256k1, :nist256p1 or :ed25519).
30 31 32 |
# File 'lib/derivator/key.rb', line 30 def curve @curve end |
#private_key ⇒ String (readonly)
Returns private key (in binary format).
33 34 35 |
# File 'lib/derivator/key.rb', line 33 def private_key @private_key end |
Class Method Details
.from_hex(private_key_hex, chain_code_hex, curve = :secp256k1) ⇒ Key
Creates new key from private key and chain code hex strings.
46 47 48 |
# File 'lib/derivator/key.rb', line 46 def self.from_hex(private_key_hex, chain_code_hex, curve = :secp256k1) new(private_key_hex.from_hex, chain_code_hex.from_hex, curve) end |
.from_seed(seed_hex, curve = :secp256k1) ⇒ Key
Creates new key from seed hex string.
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 |
# File 'lib/derivator/key.rb', line 55 def self.from_seed(seed_hex, curve = :secp256k1) seed_bytes = seed_hex.from_hex seed_key = case curve when :secp256k1 SECP256K1_SEED_KEY when :nist256p1 NIST256P1_SEED_KEY when :ed25519 ED25519_SEED_KEY end hmac = OpenSSL::HMAC.hexdigest( "SHA512", seed_key, seed_bytes ) private_key_hex = hmac[0..63] chain_code_hex = hmac[64..-1] if valid_private_key?(private_key_hex, curve) from_hex(private_key_hex, chain_code_hex, curve) else from_seed(hmac, curve) end end |
.valid_private_key?(private_key_hex, curve = :secp256k1) ⇒ true, false
Checks private key for a particular curve (used internally). Primarily checks whether key value is less than order of the curve.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/derivator/key.rb', line 89 def self.valid_private_key?(private_key_hex, curve = :secp256k1) return false unless private_key_hex =~ /\A[a-f0-9]{64}\z/ largest_key = case curve when :secp256k1 SECP256K1_LARGEST_KEY when :nist256p1 NIST256P1_LARGEST_KEY when :ed25519 ED25519_LARGEST_KEY end private_key = private_key_hex.to_i(16) private_key <= largest_key && (private_key > 0 || curve == :ed25519) end |
Instance Method Details
#==(other) ⇒ true, false
Compares with another Derivator::Key.
127 128 129 130 131 132 133 |
# File 'lib/derivator/key.rb', line 127 def ==(other) return false unless %i[private_key chain_code curve].all? { |m| other.respond_to?(m) } private_key == other.private_key && chain_code == other.chain_code && curve == other.curve end |
#chain_code_hex ⇒ String
Chain code as hex string.
138 139 140 |
# File 'lib/derivator/key.rb', line 138 def chain_code_hex @chain_code_hex ||= @chain_code.to_hex.freeze end |
#derive(path) ⇒ Object
Derive child key.
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 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/derivator/key.rb', line 190 def derive(path) return self if path == 'm' || path.empty? path = path.delete_prefix('m').delete_prefix('/') path = path.split('/') i_string = path.first unless i_string =~ /\A[0-9]+'?\z/ raise ArgumentError.new("Wrong derivation segment: #{i_string.inspect}") end i = i_string.to_i i += 2**31 if i_string[-1] == "'" if curve == :ed25519 && i < 2**31 raise ArgumentError.new("Only hardened derivation supported with ED25519, got #{i_string} instead") end if curve != :ed25519 generator = openssl_group.generator end data = if i >= 2**31 # Data for HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)) "00" + private_key_hex + ("%08x" % i) else # Data for HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) generator.mul(private_key_hex.to_i(16)).to_octet_string(:compressed).to_hex + ("%08x" % i) end data = data.from_hex hmac = OpenSSL::HMAC.hexdigest( "SHA512", chain_code, data ) derived_private_key_hex = hmac[0..63] derived_chain_code_hex = hmac[64..-1] if curve != :ed25519 derived_private_key_hex, derived_chain_code_hex = finish_derivation(derived_private_key_hex, derived_chain_code_hex, i) end new_key = self.class.from_hex(derived_private_key_hex, derived_chain_code_hex, curve) new_key.derive(path[1..-1].join('/')) end |
#fingerprint ⇒ String
BIP32 fingerprint (first 4 bytes of HASH160 of public key) as hex string.
146 147 148 |
# File 'lib/derivator/key.rb', line 146 def fingerprint @fingerprint ||= public_key.hash160[0..3].to_hex end |
#openssl_group ⇒ OpenSSL::PKey::EC::Group
Creates OpenSSL::PKey::EC::Group instance for the key’s #curve.
270 271 272 273 274 275 276 277 |
# File 'lib/derivator/key.rb', line 270 def openssl_group case curve when :secp256k1 OpenSSL::PKey::EC::Group.new('secp256k1') when :nist256p1 OpenSSL::PKey::EC::Group.new('prime256v1') end end |
#openssl_pkey ⇒ OpenSSL::PKey::EC::Point
Creates OpenSSL::PKey instance.
263 264 265 |
# File 'lib/derivator/key.rb', line 263 def openssl_pkey OpenSSL::PKey.read(to_pem) end |
#private_key_hex ⇒ String
Private key as hex string.
153 154 155 |
# File 'lib/derivator/key.rb', line 153 def private_key_hex @private_key_hex ||= @private_key.to_hex.freeze end |
#public_key ⇒ String
Public key (in binary format). Use #public_key_hex for convenience.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/derivator/key.rb', line 161 def public_key @public_key ||= begin case curve when :ed25519 openssl_pkey. public_to_der. to_hex. delete_prefix(ED25519_DER_PUBLIC_PREFIX). from_hex.freeze else openssl_pkey. public_key. to_octet_string(:compressed).freeze end end end |
#public_key_hex ⇒ String
Public key as hex string.
181 182 183 |
# File 'lib/derivator/key.rb', line 181 def public_key_hex @public_key_hex ||= public_key.to_hex.freeze end |
#to_pem ⇒ String
Exports key to PEM format.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/derivator/key.rb', line 241 def to_pem private_prefix = case curve when :secp256k1 SECP256K1_DER_PRIVATE_PREFIX when :nist256p1 NIST256P1_DER_PRIVATE_PREFIX when :ed25519 ED25519_DER_PRIVATE_PREFIX end der = (private_prefix + private_key_hex).from_hex pem = <<~END -----BEGIN PRIVATE KEY----- #{der.to_base64.strip} -----END PRIVATE KEY----- END end |