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.
- checksums.yaml +4 -4
- data/.editorconfig +3 -0
- data/.github/workflows/rspec.yml +37 -0
- data/.github/workflows/rubocop.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +7 -0
- data/CODE_OF_CONDUCT.md +105 -46
- data/Gemfile +5 -3
- data/README.md +61 -14
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/release +5 -0
- data/fabric-gateway.gemspec +29 -18
- data/lib/fabric/client.rb +134 -0
- data/lib/fabric/constants.rb +8 -0
- data/lib/fabric/contract.rb +163 -0
- data/lib/fabric/ec_crypto_suite.rb +199 -0
- data/lib/fabric/gateway.rb +28 -14
- data/lib/fabric/identity.rb +87 -0
- data/lib/fabric/network.rb +61 -0
- data/lib/fabric/proposal.rb +182 -0
- data/lib/fabric/proposed_transaction.rb +187 -0
- data/lib/fabric/version.rb +5 -0
- data/lib/fabric.rb +44 -0
- metadata +105 -14
- data/Gemfile.lock +0 -44
- data/lib/fabric/gateway/client.rb +0 -13
- data/lib/fabric/gateway/constants.rb +0 -20
- data/lib/fabric/gateway/ec_crypto_suite.rb +0 -172
- data/lib/fabric/gateway/identity.rb +0 -47
- data/lib/fabric/gateway/proposal.rb +0 -105
- data/lib/fabric/gateway/version.rb +0 -5
@@ -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,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
|
data/lib/fabric/gateway.rb
CHANGED
@@ -1,21 +1,35 @@
|
|
1
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
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
|