Module: XRBP::Crypto::Key

Defined in:
lib/xrbp/crypto/key.rb

Constant Summary collapse

TOKEN_TYPES =
{
  :none             =>  1, # unused
  :node_public      => 28,
  :node_private     => 32,
  :account_id       =>  0,
  :account_public   => 35,
  :account_secret   => 34,
  :family_generator => 41,
  :family_seed      => 33
}

Class Method Summary collapse

Class Method Details

.ed25519(seed = nil) ⇒ Hash

Returns new ed25519 key pair (both public and private components).

Returns:

  • (Hash)

    new ed25519 key pair (both public and private components)


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

def self.ed25519(seed=nil)
  # XXX openssl 1.1.1 needed for EdDSA support:
  #     https://www.openssl.org/blog/blog/2018/09/11/release111/
  #     Until then use this:
  require "ed25519"

  sd, pk = nil
  if seed
    sd,pk = seed,seed

  else
    sd = Crypto.seed[:seed]
    pk = Crypto.parse_seed(sd)
  end

  sha512 = OpenSSL::Digest::SHA512.new
  pk = sha512.digest(pk)[0..31]

  key = Ed25519::SigningKey.new(pk)
  {  :public => key.verify_key.to_bytes.unpack("H*").first.upcase,
    :private => key.to_bytes.unpack("H*").first.upcase,
       :seed => sd,
       :type => :ed25519 }
end

.secp256k1(seed = nil) ⇒ Hash

Returns new secp256k1 key pair (both public and private components).

Returns:

  • (Hash)

    new secp256k1 key pair (both public and private components)


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
57
58
59
60
# File 'lib/xrbp/crypto/key.rb', line 20

def self.secp256k1(seed=nil)
    # XXX: the bitcoin secp256k1 implementation (which rippled pulls in / vendors)
    #      has alot of nuances which require special configuration in openssl. For
    #      the time being, mitigate this by pulling in & using the ruby
    #      btc-secp256k1 bindings:
    #        https://github.com/cryptape/ruby-bitcoin-secp256k1
    #
    #      Perhaps at some point, we can look into implementing this logic in pure-ruby:
    #        https://medium.com/coinmonks/introduction-to-blockchains-bedrock-the-elliptic-curve-secp256k1-e4bd3bc17d
    require 'secp256k1'

    spk = Secp256k1::PrivateKey.new

    sd, pk = nil
    if seed
      sd,pk = seed,seed

    else
      sd = Crypto.seed[:seed]
      pk = Crypto.parse_seed(sd)
    end

    # FIXME: rippled & ripple-keypairs (& by extension ripple-lib) repeatedly
    #        hash seed until certain it is less than the order of the
    #        curve, we should do this as well (for security)
    #
    #        https://github.com/ripple/rippled/blob/develop/src/ripple/crypto/impl/GenerateDeterministicKey.cpp
    #        https://github.com/ripple/ripple-keypairs/blob/master/src/secp256k1.js
    #
    #        Also look into if setting raw key here has same effect as
    #        secp256k1.mul (as invoked in keypairs)
    sha512 = OpenSSL::Digest::SHA512.new
    pk = sha512.digest(pk)[0..31]

    spk.set_raw_privkey pk

    {  :public => spk.pubkey.serialize.unpack("H*").first,
      :private => spk.send(:serialize),
         :seed => sd,
         :type => :secp256k1 }
end

.sign_digest(key, data) ⇒ String

Sign the digest using the specified key, returning the result

Parameters:

  • key (Hash)

    key to sign digest with

  • data (String)

    data to sign (must be exactly 32 bytes long!)

Returns:

  • (String)

    signed digest


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/xrbp/crypto/key.rb', line 95

def self.sign_digest(key, data)
  raise "unknown key" unless key.is_a?(Hash) && key[:type] && key[:private]
  raise "invalid data" unless data.length == 32

  if key[:type] == :secp256k1
    require 'secp256k1'

    pk = Secp256k1::PrivateKey.new
    pk.set_raw_privkey [key[:private]].pack("H*")
    sig_raw = pk.ecdsa_sign data, raw: true
    return pk.ecdsa_serialize sig_raw

  elsif key[:type] == :ed25519
    require "ed25519"

    sd = key[:seed]
    pk = Crypto.parse_seed(sd)

    sha512 = OpenSSL::Digest::SHA512.new
    pk = sha512.digest(pk)[0..31]

    pk = Ed25519::SigningKey.new(pk)
    return pk.sign(data)
  end

  raise "unknown key type"
end

.verify(key, data, expected) ⇒ Bool

Returns bool indicating if data is the result of signing expected value with given key.

Parameters:

  • key (Hash)

    key to use to verify digest

  • data (String)

    signed data

  • expected (String)

    original unsigned data

Returns:

  • (Bool)

    indicating if signed digest matches original data


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

def self.verify(key, data, expected)
  if key[:type] == :secp256k1
    require 'secp256k1'

    pb = Secp256k1::PublicKey.new :pubkey => [key[:public]].pack("H*"),
                                     :raw => true
    pv = Secp256k1::PrivateKey.new

    return pb.ecdsa_verify expected,
           pv.ecdsa_deserialize(data), raw: true

  elsif key[:type] == :ed25519
    require "ed25519"

    pk = Ed25519::VerifyKey.new([key[:public]].pack("H*"))
    begin
      return pk.verify(data, expected)
    rescue Ed25519::VerifyError
      return false
    end
  end

  raise "unknown key type"
end