fabric-gateway 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Gateway Client represents the connection to a Hyperledger Fabric Gateway.
6
+ #
7
+ class Client
8
+ attr_reader :grpc_client, :default_call_options
9
+
10
+ #
11
+ # Initializes a client
12
+ #
13
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize
14
+ #
15
+ # @param [Gateway::Gateway::Stub] grpc_client grpc gateway client stub
16
+ # @param [string] host hostname and port of the gateway
17
+ # @param [GRPC::Core::ChannelCredentials|GRPC::Core::XdsChannelCredentials|Symbol] creds channel credentials
18
+ # (usually the CA certificate)
19
+ # @param [Hash] default_call_options call options to use by default for different operations
20
+ # @option default_call_options [Hash] :endorse_options default options for endorse call; @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
21
+ # @option default_call_options [Hash] :evaluate_options default options for evaluate call; @see keyword arguments in
22
+ # https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
23
+ # @option default_call_options [Hash] :submit_options default options for submit call; @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
24
+ # @option default_call_options [Hash] :commit_status_options default options for commit_status call;
25
+ # @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
26
+ # @option default_call_options [Hash] :chaincode_events_options default options for chaincode_events call;
27
+ # @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
28
+ # @param [Hash] **client_opts client initialization options; @see keyword arguments at https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize
29
+ #
30
+ def initialize(grpc_client: nil, host: nil, creds: nil, default_call_options: {}, **client_opts)
31
+ if grpc_client
32
+ init_stub grpc_client
33
+ elsif host && creds
34
+ init_grpc_args(host, creds, **client_opts)
35
+ else
36
+ raise InvalidArgument, 'Must pass a Gateway::Gateway::Stub or <host>, <creds>, <client_opts>'
37
+ end
38
+ init_call_options(default_call_options)
39
+ end
40
+
41
+ #
42
+ # Submits an evaluate_request to the gateway to be evaluted.
43
+ #
44
+ # @param [Gateway::EvaluateRequest] evaluate_request
45
+ # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
46
+ #
47
+ # @return [Gateway::EvaluateResponse] evaluate_response
48
+ #
49
+ def evaluate(evaluate_request, options = {})
50
+ @grpc_client.evaluate(evaluate_request, @default_call_options[:evaluate_options].merge(options))
51
+ end
52
+
53
+ #
54
+ # Submits an endorse_request to the gateway to be evaluted.
55
+ #
56
+ # @param [Gateway::EndorseRequest] endorse_request
57
+ # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
58
+ #
59
+ # @return [Gateway::EndorseResponse] endorse_response
60
+ #
61
+ def endorse(endorse_request, options = {})
62
+ @grpc_client.endorse(endorse_request, @default_call_options[:endorse_options].merge(options))
63
+ end
64
+
65
+ #
66
+ # Submits an submit_request to the gateway to be evaluted.
67
+ #
68
+ # @param [Gateway::SubmitRequest] submit_request
69
+ # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
70
+ #
71
+ # @return [Gateway::SubmitResponse] submit_response
72
+ #
73
+ def submit(submit_request, options = {})
74
+ @grpc_client.submit(submit_request, @default_call_options[:submit_options].merge(options))
75
+ end
76
+
77
+ #
78
+ # Submits an commit_status_request to the gateway to be evaluted.
79
+ #
80
+ # @param [Gateway::CommitStatusRequest] commit_status_request
81
+ # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
82
+ #
83
+ # @return [Gateway::CommitStatusResponse] commit_status_response
84
+ #
85
+ def commit_status(commit_status_request, options = {})
86
+ @grpc_client.commit_status(commit_status_request, @default_call_options[:commit_status_options].merge(options))
87
+ end
88
+
89
+ #
90
+ # Subscribe to chaincode events
91
+ #
92
+ # @NOTE: This function has never been utilized or tested. This function is probably wrong, missing a block.
93
+ # @TODO: add testing!
94
+ #
95
+ # @param [Gateway::ChaincodeEventsRequest] chaincode_events_request
96
+ # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:server_streamer
97
+ #
98
+ # @return [Gateway::ChaincodeEventsResponse] commit_status_response (probably wrong, this is a stream.)
99
+ #
100
+ def chaincode_events(chaincode_events_request, options = {}, &block)
101
+ @grpc_client.chaincode_events(chaincode_events_request,
102
+ @default_call_options[:chaincode_events_options].merge(options), &block)
103
+ end
104
+
105
+ private
106
+
107
+ def init_stub(stub)
108
+ unless stub.is_a? ::Gateway::Gateway::Stub
109
+ raise InvalidArgument, 'Must pass a Gateway::Gateway::Stub or <host>, <creds>, <client_opts>'
110
+ end
111
+
112
+ @grpc_client = stub
113
+ end
114
+
115
+ def init_grpc_args(host, creds, **client_opts)
116
+ unless creds.is_a?(GRPC::Core::ChannelCredentials) ||
117
+ creds.is_a?(GRPC::Core::XdsChannelCredentials) ||
118
+ creds.is_a?(Symbol)
119
+ raise InvalidArgument, 'creds is not a ChannelCredentials, XdsChannelCredentials, or Symbol'
120
+ end
121
+
122
+ @grpc_client = ::Gateway::Gateway::Stub.new(host, creds, **client_opts)
123
+ end
124
+
125
+ def init_call_options(call_options)
126
+ @default_call_options = call_options
127
+ @default_call_options[:endorse_options] ||= {}
128
+ @default_call_options[:evaluate_options] ||= {}
129
+ @default_call_options[:submit_options] ||= {}
130
+ @default_call_options[:commit_status_options] ||= {}
131
+ @default_call_options[:chaincode_events_options] ||= {}
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ module Constants
5
+ ## Variables
6
+ CHANNEL_HEADER_VERSION = 1
7
+ end
8
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Contract represents a smart contract, and allows applications to:
6
+ #
7
+ # - Evaluate transactions that query state from the ledger using the EvaluateTransaction() method.
8
+ #
9
+ # - Submit transactions that store state to the ledger using the SubmitTransaction() method.
10
+ #
11
+ # For more complex transaction invocations, such as including transient data, transactions can be evaluated or
12
+ # submitted using the Evaluate() or Submit() methods respectively. The result of a submitted transaction can be
13
+ # accessed prior to its commit to the ledger using SubmitAsync().
14
+ #
15
+ # By default, proposal, transaction and commit status messages will be signed using the signing implementation
16
+ # specified when connecting the Gateway. In cases where an external client holds the signing credentials, a signing
17
+ # implementation can be omitted when connecting the Gateway and off-line signing can be carried out by:
18
+ #
19
+ # 1. Returning the serialized proposal, transaction or commit status along with its digest to the client for
20
+ # them to generate a signature.
21
+ #
22
+ # 2. With the serialized message and signature received from the client to create a signed proposal, transaction or
23
+ # commit using the Gateway's NewSignedProposal(), NewSignedTransaction() or NewSignedCommit() methods respectively.
24
+ #
25
+ class Contract
26
+ attr_reader :network, :chaincode_name, :contract_name
27
+
28
+ def initialize(network, chaincode_name, contract_name = '')
29
+ @network = network
30
+ @chaincode_name = chaincode_name
31
+ @contract_name = contract_name
32
+ end
33
+
34
+ def client
35
+ network.client
36
+ end
37
+
38
+ def signer
39
+ network.signer
40
+ end
41
+
42
+ def gateway
43
+ network.gateway
44
+ end
45
+
46
+ def network_name
47
+ network.name
48
+ end
49
+
50
+ #
51
+ # Evaluate a transaction function and return its results. A transaction proposal will be evaluated on endorsing
52
+ # peers but the transaction will not be sent to the ordering service and so will not be committed to the ledger.
53
+ # This can be used for querying the world state.
54
+ #
55
+ # @param [String] transaction_name
56
+ # @param [Array] arguments array of arguments to pass to the transaction
57
+ #
58
+ # @return [String] raw payload of the transaction response
59
+ #
60
+ def evaluate_transaction(transaction_name, arguments = [])
61
+ evaluate(transaction_name, { arguments: arguments })
62
+ end
63
+
64
+ #
65
+ # Submit a transaction to the ledger and return its result only after it is committed to the ledger. The
66
+ # transaction function will be evaluated on endorsing peers and then submitted to the ordering service to be
67
+ # committed to the ledger.
68
+ #
69
+ # @TODO: Not yet complete
70
+ #
71
+ # @param [String] transaction_name
72
+ # @param [Array] arguments array of arguments to pass to the transaction
73
+ #
74
+ # @return [String] raw payload of the transaction response
75
+ #
76
+ def submit_transaction(transaction_name, arguments = [])
77
+ submit(transaction_name, { arguments: arguments })
78
+ end
79
+
80
+ #
81
+ # Evaluate a transaction function and return its result. This method provides greater control over the transaction
82
+ # proposal content and the endorsing peers on which it is evaluated. This allows transaction functions to be
83
+ # evaluated where the proposal must include transient data, or that will access ledger data with key-based
84
+ # endorsement policies.
85
+ #
86
+ # @param [String] transaction_name
87
+ # @param [Hash] proposal_options
88
+ # @option proposal_options [Array] :arguments array of arguments to pass to the transaction
89
+ # @option proposal_options [Hash] :transient_data Private data passed to the transaction function but not recorded
90
+ # on the ledger.
91
+ # @option proposal_options [Array] :endorsing_organizations Specifies the set of organizations that will attempt to
92
+ # endorse the proposal.
93
+ #
94
+ # @return [String] Raw evaluation response payload
95
+ #
96
+ def evaluate(transaction_name, proposal_options = {})
97
+ new_proposal(transaction_name, **proposal_options).evaluate
98
+ end
99
+
100
+ #
101
+ # Submit a transaction to the ledger and return its result only after it is committed to the ledger. The
102
+ # transaction function will be evaluated on endorsing peers and then submitted to the ordering service to be
103
+ # committed to the ledger.
104
+ #
105
+ # @TODO: Implement Me! - LEFT OFF HERE!
106
+ #
107
+ # @param [String] transaction_name
108
+ # @param [Hash] proposal_options
109
+ # @option proposal_options [Array] :arguments array of arguments to pass to the transaction
110
+ # @option proposal_options [Hash] :transient_data Private data passed to the transaction function but not recorded
111
+ # on the ledger.
112
+ # @option proposal_options [Array] :endorsing_organizations Specifies the set of organizations that will attempt to
113
+ # endorse the proposal.
114
+ #
115
+ # @return [String] Raw evaluation response payload
116
+ #
117
+ def submit(transaction_name, proposal_options = {})
118
+ transaction = new_proposal(transaction_name, **proposal_options).endorse
119
+ submitted = transaction.submit
120
+
121
+ status = submitted.get_status
122
+
123
+ raise CommitError, status unless status.get_status == ::GRPC::Core::StatusCodes::OK
124
+ end
125
+
126
+ #
127
+ # @TODO: unimplemented, not sure if this can be implemented because
128
+ # the official grpc ruby client does not support non-blocking async
129
+ # calls (https://github.com/grpc/grpc/issues/10973)
130
+ #
131
+ # not 100% sure if grpc support is necessary for this.
132
+ #
133
+ def submit_async
134
+ raise NotYetImplemented
135
+ end
136
+
137
+ #
138
+ # Creates a transaction proposal that can be evaluated or endorsed. Supports off-line signing flow.
139
+ #
140
+ # @param [String] transaction_name transaction name (first argument unshifted into the argument array)
141
+ # @param [Array<String>] arguments array of arguments to pass to the transaction
142
+ # @param [Hash] transient_data Private data passed to the transaction function but not recorded on the ledger.
143
+ # @param [Array] endorsing_organizations Specifies the set of organizations that will attempt to endorse the
144
+ # proposal.
145
+ #
146
+ # @return [Fabric::Proposal] signed unexecuted proposal
147
+ #
148
+ def new_proposal(transaction_name, arguments: [], transient_data: {}, endorsing_organizations: [])
149
+ proposed_transaction = ProposedTransaction.new(
150
+ self,
151
+ qualified_transaction_name(transaction_name),
152
+ arguments: arguments,
153
+ transient_data: transient_data,
154
+ endorsing_organizations: endorsing_organizations
155
+ )
156
+ Proposal.new(proposed_transaction)
157
+ end
158
+
159
+ def qualified_transaction_name(transaction_name)
160
+ contract_name.nil? || contract_name.empty? ? transaction_name : "#{contract_name}:#{transaction_name}"
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ # Adapted from:
6
+ # https://github.com/kirshin/hyperledger-fabric-sdk/blob/95a5a1a37001852312df25946e960a9ff149207e/lib/fabric/crypto_suite.rb
7
+ module Fabric
8
+ #
9
+ # Elliptic-curve Crypto Suite using OpenSSL
10
+ #
11
+ # @TODO missing tests
12
+ class ECCryptoSuite # rubocop:disable Metrics/ClassLength
13
+ DEFAULT_KEY_SIZE = 256
14
+ DEFAULT_DIGEST_ALGORITHM = 'SHA256'
15
+ DEFAULT_AES_KEY_SIZE = 128
16
+
17
+ EC_CURVES = { 256 => 'prime256v1', 384 => 'secp384r1' }.freeze
18
+
19
+ CIPHER = 'aes-256-cbc'
20
+
21
+ attr_reader :key_size, :digest_algorithm, :digest_instance, :curve, :cipher
22
+
23
+ def initialize(opts = {})
24
+ @key_size = opts[:key_size] || DEFAULT_KEY_SIZE
25
+ @digest_algorithm = opts[:digest_algorithm] || DEFAULT_DIGEST_ALGORITHM
26
+ @digest_instance = OpenSSL::Digest.new digest_algorithm
27
+ @curve = EC_CURVES[key_size]
28
+ @cipher = opts[:cipher] || CIPHER
29
+ end
30
+
31
+ def sign(private_key, message)
32
+ digest = digest message
33
+ key = pkey_from_private_key private_key
34
+ signature = key.dsa_sign_asn1 digest
35
+ sequence = OpenSSL::ASN1.decode signature
36
+ sequence = prevent_malleability sequence, key.group.order
37
+
38
+ sequence.to_der
39
+ end
40
+
41
+ def verify(public_key, message, signature)
42
+ digest = digest message
43
+ openssl_pkey = openssl_pkey_from_public_key public_key
44
+ sequence = OpenSSL::ASN1.decode signature
45
+ return false unless check_malleability sequence, openssl_pkey.group.order
46
+
47
+ openssl_pkey.dsa_verify_asn1(digest, signature)
48
+ end
49
+
50
+ def generate_private_key
51
+ key = OpenSSL::PKey::EC.new curve
52
+ key.generate_key!
53
+
54
+ key.private_key.to_s(16).downcase
55
+ end
56
+
57
+ def generate_csr(private_key, attrs = [])
58
+ key = pkey_from_private_key private_key
59
+
60
+ req = OpenSSL::X509::Request.new
61
+ req.public_key = key
62
+ req.subject = OpenSSL::X509::Name.new attrs
63
+ req.sign key, @digest_instance
64
+
65
+ req
66
+ end
67
+
68
+ def generate_nonce(length = 24)
69
+ OpenSSL::Random.random_bytes length
70
+ end
71
+
72
+ def hexdigest(message)
73
+ @digest_instance.hexdigest message
74
+ end
75
+
76
+ def digest(message)
77
+ @digest_instance.digest message
78
+ end
79
+
80
+ def encode_hex(bytes)
81
+ bytes.unpack1('H*')
82
+ end
83
+
84
+ def decode_hex(string)
85
+ [string].pack('H*')
86
+ end
87
+
88
+ def restore_public_key(private_key)
89
+ private_bn = OpenSSL::BN.new private_key, 16
90
+ group = OpenSSL::PKey::EC::Group.new curve
91
+ public_bn = group.generator.mul(private_bn).to_bn
92
+ public_bn = OpenSSL::PKey::EC::Point.new(group, public_bn).to_bn
93
+
94
+ public_bn.to_s(16).downcase
95
+ end
96
+
97
+ def address_from_public_key(public_key)
98
+ bytes = decode_hex public_key
99
+ address_bytes = digest(bytes[1..])[-20..]
100
+
101
+ encode_hex address_bytes
102
+ end
103
+
104
+ def build_shared_key(private_key, public_key)
105
+ pkey = pkey_from_private_key private_key
106
+ public_bn = OpenSSL::BN.new public_key, 16
107
+ group = OpenSSL::PKey::EC::Group.new curve
108
+ public_point = OpenSSL::PKey::EC::Point.new group, public_bn
109
+
110
+ encode_hex pkey.dh_compute_key(public_point)
111
+ end
112
+
113
+ def encrypt(secret, data)
114
+ aes = OpenSSL::Cipher.new cipher
115
+ aes.encrypt
116
+ aes.key = decode_hex(secret)
117
+ iv = aes.random_iv
118
+ aes.iv = iv
119
+
120
+ Base64.strict_encode64(iv + aes.update(data) + aes.final)
121
+ end
122
+
123
+ def decrypt(secret, data)
124
+ return unless data
125
+
126
+ encrypted_data = Base64.strict_decode64 data
127
+ aes = OpenSSL::Cipher.new cipher
128
+ aes.decrypt
129
+ aes.key = decode_hex(secret)
130
+ aes.iv = encrypted_data[0..15]
131
+ encrypted_data = encrypted_data[16..]
132
+
133
+ aes.update(encrypted_data) + aes.final
134
+ end
135
+
136
+ def pkey_pem_from_private_key(private_key)
137
+ public_key = restore_public_key private_key
138
+ key = OpenSSL::PKey::EC.new curve
139
+ key.private_key = OpenSSL::BN.new private_key, 16
140
+ key.public_key = OpenSSL::PKey::EC::Point.new key.group,
141
+ OpenSSL::BN.new(public_key, 16)
142
+
143
+ pkey = OpenSSL::PKey::EC.new(key.public_key.group)
144
+ pkey.public_key = key.public_key
145
+
146
+ pkey.to_pem
147
+ end
148
+
149
+ def key_from_pem(pem)
150
+ key = OpenSSL::PKey::EC.new(pem)
151
+ key.private_key.to_s(16).downcase
152
+ end
153
+
154
+ def pkey_from_x509_certificate(certificate)
155
+ cert = OpenSSL::X509::Certificate.new(certificate)
156
+ cert.public_key.public_key.to_bn.to_s(16).downcase
157
+ end
158
+
159
+ def openssl_pkey_from_public_key(public_key)
160
+ pkey = OpenSSL::PKey::EC.new curve
161
+ pkey.public_key = OpenSSL::PKey::EC::Point.new(pkey.group, OpenSSL::BN.new(public_key, 16))
162
+
163
+ pkey
164
+ end
165
+
166
+ private
167
+
168
+ def pkey_from_private_key(private_key)
169
+ public_key = restore_public_key private_key
170
+ key = OpenSSL::PKey::EC.new curve
171
+ key.private_key = OpenSSL::BN.new private_key, 16
172
+ key.public_key = OpenSSL::PKey::EC::Point.new key.group,
173
+ OpenSSL::BN.new(public_key, 16)
174
+
175
+ key
176
+ end
177
+
178
+ # barely understand this code - this link provides a good explanation:
179
+ # http://coders-errand.com/malleability-ecdsa-signatures/
180
+ def prevent_malleability(sequence, order)
181
+ half_order = order >> 1
182
+
183
+ if (half_key = sequence.value[1].value) > half_order
184
+ sequence.value[1].value = order - half_key
185
+ end
186
+
187
+ sequence
188
+ end
189
+
190
+ # ported from python code, understanding extremely limited.
191
+ # from what I gather, sequence.value[0] and sequence.value[1]
192
+ # are the r and s values from the python implementation
193
+ # https://github.com/hyperledger/fabric-sdk-py/blob/25209f61518873da68d28313582607c29b5bae7d/hfc/util/crypto/crypto.py#L259
194
+ def check_malleability(sequence, order)
195
+ half_order = order >> 1
196
+ sequence.value[1].value <= half_order
197
+ end
198
+ end
199
+ end
@@ -1,21 +1,35 @@
1
- require "fabric/gateway/client"
2
- require "fabric/gateway/constants"
3
- require 'fabric/gateway/ec_crypto_suite'
4
- require 'fabric/gateway/identity'
5
- require "fabric/gateway/proposal"
6
- require "fabric/gateway/version"
7
-
8
-
9
- require "gateway/gateway_pb"
10
- require "gateway/gateway_services_pb"
1
+ # frozen_string_literal: true
11
2
 
12
3
  module Fabric
13
- module Gateway
14
- class Error < StandardError; end
4
+ #
5
+ # Gateway represents the connection of a specific client identity to a Fabric Gateway.
6
+ #
7
+ class Gateway
8
+ attr_reader :signer, :client
15
9
 
10
+ #
11
+ # Initialize a new Gateway
12
+ #
13
+ # @param [Fabric::Identity] signer identity utilized to sign transactions
14
+ # @param [Fabric::Client] client Gateway Client
15
+ #
16
+ def initialize(signer, client)
17
+ raise InvalidArgument, 'signer must be Fabric::Identity' unless signer.is_a? Fabric::Identity
18
+ raise InvalidArgument, 'client must be Fabric::Client' unless client.is_a? Fabric::Client
19
+
20
+ @signer = signer
21
+ @client = client
22
+ end
16
23
 
17
- def self.crypto_suite(opts = {})
18
- @crypto_suite ||= Fabric::Gateway::ECCryptoSuite.new opts
24
+ #
25
+ # Initialize new network from the Gateway
26
+ #
27
+ # @param [string] name channel name
28
+ #
29
+ # @return [Fabric::Network] returns a new network
30
+ #
31
+ def new_network(name)
32
+ Network.new(self, name)
19
33
  end
20
34
  end
21
35
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msp/identities_pb'
4
+ require 'base64'
5
+
6
+ # Adapted from:
7
+ # https://github.com/kirshin/hyperledger-fabric-sdk/blob/95a5a1a37001852312df25946e960a9ff149207e/lib/fabric/identity.rb
8
+
9
+ module Fabric
10
+ #
11
+ # @attr_reader [String] private_key raw private key in hex format
12
+ # @attr_reader [String] public_key raw public key in hex format
13
+ # @attr_reader [String] certificate raw certificate in pem format
14
+ # @attr_reader [String] msp_id MSP (Membership Service Provider) Identifier
15
+ #
16
+ class Identity
17
+ attr_reader :private_key,
18
+ :public_key,
19
+ :address, # TODO: possibly unnecessary
20
+ :crypto_suite
21
+
22
+ attr_accessor :certificate, :msp_id
23
+
24
+ def initialize(private_key: nil, public_key: nil, certificate: nil, msp_id: nil, crypto_suite: nil)
25
+ @crypto_suite = crypto_suite || Fabric.crypto_suite
26
+
27
+ @private_key = private_key || @crypto_suite.generate_private_key
28
+ @public_key = public_key || @crypto_suite.restore_public_key(@private_key)
29
+ @certificate = certificate
30
+ @msp_id = msp_id
31
+
32
+ @address = @crypto_suite.address_from_public_key @public_key
33
+
34
+ return unless @certificate
35
+
36
+ raise Fabric::Error, 'Key mismatch (public_key or certificate) for identity' unless validate_key_integrity
37
+ end
38
+
39
+ #
40
+ # Validates that the private_key, public_key, and certificate are valid and match
41
+ #
42
+ # @return [boolean] true if valid, false otherwise
43
+ #
44
+ def validate_key_integrity
45
+ cert_pubkey = @crypto_suite.pkey_from_x509_certificate(certificate)
46
+ priv_pubkey = @crypto_suite.restore_public_key(@private_key)
47
+
48
+ @public_key == cert_pubkey && @public_key == priv_pubkey
49
+ end
50
+
51
+ def generate_csr(attrs = [])
52
+ @crypto_suite.generate_csr private_key, attrs
53
+ end
54
+
55
+ def sign(message)
56
+ @crypto_suite.sign(private_key, message)
57
+ end
58
+
59
+ def digest(message)
60
+ @crypto_suite.digest message
61
+ end
62
+
63
+ # TODO: Do we need this?
64
+ def shared_secret_by(public_key)
65
+ @crypto_suite.build_shared_key private_key, public_key
66
+ end
67
+
68
+ def as_proto
69
+ @as_proto ||= Msp::SerializedIdentity.new(mspid: msp_id, id_bytes: certificate)
70
+ end
71
+
72
+ def to_proto
73
+ @to_proto ||= Msp::SerializedIdentity.new(mspid: msp_id, id_bytes: certificate).to_proto
74
+ end
75
+
76
+ #
77
+ # Creates a new gateway passing in the current identity
78
+ #
79
+ # @param [Gateway::Gateway::Stub] connection <description>
80
+ #
81
+ # @return [Fabric::Gateway] <description>
82
+ #
83
+ def new_gateway(client)
84
+ Fabric::Gateway.new(self, client)
85
+ end
86
+ end
87
+ end