fabric-gateway 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|