fabric-gateway 0.0.2 → 0.4.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/codeql-analysis.yml +71 -0
- data/.github/workflows/rspec.yml +37 -0
- data/.github/workflows/rubocop.yml +28 -0
- data/.github/workflows/todo.yml +10 -0
- data/.github/workflows/yardoc.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +7 -0
- data/.yardopts +8 -0
- data/CODE_OF_CONDUCT.md +105 -46
- data/Gemfile +5 -3
- data/LICENSE.txt +1 -1
- data/README.md +123 -12
- data/Rakefile +6 -3
- data/bin/console +4 -3
- data/bin/regenerate +1 -0
- data/bin/release +5 -0
- data/fabric-gateway.gemspec +31 -17
- data/lib/fabric/accessors/contract.rb +51 -0
- data/lib/fabric/accessors/gateway.rb +33 -0
- data/lib/fabric/accessors/network.rb +40 -0
- data/lib/fabric/client.rb +199 -0
- data/lib/fabric/constants.rb +8 -0
- data/lib/fabric/contract.rb +178 -0
- data/lib/fabric/ec_crypto_suite.rb +199 -0
- data/lib/fabric/entities/chaincode_events_requests.rb +166 -0
- data/lib/fabric/entities/envelope.rb +158 -0
- data/lib/fabric/entities/identity.rb +87 -0
- data/lib/fabric/entities/proposal.rb +189 -0
- data/lib/fabric/entities/proposed_transaction.rb +163 -0
- data/lib/fabric/entities/status.rb +32 -0
- data/lib/fabric/entities/transaction.rb +247 -0
- data/lib/fabric/gateway.rb +31 -6
- data/lib/fabric/network.rb +70 -0
- data/lib/fabric/version.rb +5 -0
- data/lib/fabric.rb +59 -0
- data/lib/msp/identities_pb.rb +25 -0
- metadata +162 -13
- data/Gemfile.lock +0 -42
- data/lib/fabric/gateway/version.rb +0 -5
@@ -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
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fabric
|
4
|
+
#
|
5
|
+
# Encapsulates a Chaincode Events Request protobuf message
|
6
|
+
#
|
7
|
+
class ChaincodeEventsRequest
|
8
|
+
attr_reader :contract,
|
9
|
+
:start_block
|
10
|
+
|
11
|
+
# @!parse include Fabric::Accessors::Network
|
12
|
+
# @!parse include Fabric::Accessors::Gateway
|
13
|
+
include Fabric::Accessors::Contract
|
14
|
+
|
15
|
+
#
|
16
|
+
# Creates a new ChaincodeEventsRequest
|
17
|
+
#
|
18
|
+
# @param [Fabric::Contract] contract an instance of a contract
|
19
|
+
# @param [Integer] start_block Block number at which to start reading chaincode events.
|
20
|
+
#
|
21
|
+
def initialize(contract, start_block: nil)
|
22
|
+
@contract = contract
|
23
|
+
@start_block = start_block
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Returns the signed request
|
28
|
+
#
|
29
|
+
# @return [Gateway::SignedChaincodeEventsRequest] generated signed request
|
30
|
+
#
|
31
|
+
def signed_request
|
32
|
+
@signed_request ||= ::Gateway::SignedChaincodeEventsRequest.new(request: chaincode_events_request.to_proto)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns the chaincode events request
|
37
|
+
#
|
38
|
+
# @return [Gateway::ChaincodeEventsRequest] chaincode events request - controls what events are returned
|
39
|
+
# from a chaincode events request
|
40
|
+
#
|
41
|
+
def chaincode_events_request
|
42
|
+
@chaincode_events_request ||= new_chaincode_events_request
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Get the serialized chaincode events request protobuffer message.
|
47
|
+
#
|
48
|
+
# @return [String] protobuffer serialized chaincode events request
|
49
|
+
#
|
50
|
+
def request_bytes
|
51
|
+
signed_request.request
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Get the digest of the chaincode events request. This is used to generate a digital signature.
|
56
|
+
#
|
57
|
+
# @return [String] chaincode events request digest
|
58
|
+
#
|
59
|
+
def request_digest
|
60
|
+
Fabric.crypto_suite.digest(request_bytes)
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Sets the signed request signature.
|
65
|
+
#
|
66
|
+
# @param [String] signature
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
#
|
70
|
+
def signature=(signature)
|
71
|
+
signed_request.signature = signature
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Returns the signed_request signature
|
76
|
+
#
|
77
|
+
# @return [String] Raw byte string signature
|
78
|
+
#
|
79
|
+
def signature
|
80
|
+
signed_request.signature
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Sign the chaincode events request; Noop if request already signed.
|
85
|
+
#
|
86
|
+
# @return [void]
|
87
|
+
#
|
88
|
+
def sign
|
89
|
+
return if signed?
|
90
|
+
|
91
|
+
self.signature = signer.sign(request_bytes)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Checks if the signed chaincode events has been signed.
|
96
|
+
#
|
97
|
+
# @return [Boolean] true if the signed chaincode events has been signed; otherwise false.
|
98
|
+
#
|
99
|
+
def signed?
|
100
|
+
!signed_request.signature.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Get chaincode events emitted by transaction functions of a specific chaincode.
|
105
|
+
#
|
106
|
+
# @see Fabric::Client#chaincode_events Fabric::Client#chaincode_events - explanation of the different return types
|
107
|
+
# and example usage.
|
108
|
+
# @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:server_streamer Call options for options parameter
|
109
|
+
#
|
110
|
+
# @param [Hash] options gRPC call options (merged with default_call_options from initializer)
|
111
|
+
# @yield [chaincode_event] loops through the chaincode events
|
112
|
+
# @yieldparam [Gateway::ChaincodeEventsResponse] chaincode_event the chaincode event
|
113
|
+
#
|
114
|
+
# @return [Enumerator|GRPC::ActiveCall::Operation|nil] Dependent on parameters passed;
|
115
|
+
# please see Fabric::Client#get_chaincode_events
|
116
|
+
#
|
117
|
+
def get_events(options = {}, &block)
|
118
|
+
sign
|
119
|
+
|
120
|
+
client.chaincode_events(signed_request, options, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
#
|
126
|
+
# Generates a new chaincode events request
|
127
|
+
#
|
128
|
+
# @return [Gateway::ChaincodeEventsRequest] chaincode events request - controls what events are returned
|
129
|
+
#
|
130
|
+
def new_chaincode_events_request
|
131
|
+
::Gateway::ChaincodeEventsRequest.new(
|
132
|
+
channel_id: network_name,
|
133
|
+
chaincode_id: chaincode_name,
|
134
|
+
identity: signer.to_proto,
|
135
|
+
start_position: start_position
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Generates the start_position for the chaincode events request or returns the cached start_position
|
141
|
+
#
|
142
|
+
# @return [Orderer::SeekPosition] start position for the chaincode events request
|
143
|
+
#
|
144
|
+
def start_position
|
145
|
+
@start_position ||= new_start_position
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Generates the start position for the chaincode events request; if no start_block is specified,
|
150
|
+
# generates a seek next commit start position, otherwise generates a start_position to the start_block
|
151
|
+
#
|
152
|
+
# @return [Orderer::SeekPosition] start position for the chaincode events request
|
153
|
+
#
|
154
|
+
def new_start_position
|
155
|
+
specified = nil
|
156
|
+
next_commit = nil
|
157
|
+
|
158
|
+
if start_block
|
159
|
+
specified = ::Orderer::SeekSpecified.new(number: start_block)
|
160
|
+
else
|
161
|
+
next_commit = ::Orderer::SeekNextCommit.new
|
162
|
+
end
|
163
|
+
Orderer::SeekPosition.new(specified: specified, next_commit: next_commit)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fabric
|
4
|
+
#
|
5
|
+
# Encapsulates an Envelop protobuf message
|
6
|
+
#
|
7
|
+
class Envelope
|
8
|
+
# @return [Common::Envelope] transaction envelope
|
9
|
+
attr_reader :envelope
|
10
|
+
|
11
|
+
#
|
12
|
+
# Creates a new Envelope instance.
|
13
|
+
#
|
14
|
+
# @param [Common::Envelope] envelope
|
15
|
+
#
|
16
|
+
def initialize(envelope)
|
17
|
+
@envelope = envelope
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Checks if the envelope has been signed.
|
22
|
+
#
|
23
|
+
# @return [Boolean] true if the envelope has been signed; otherwise false.
|
24
|
+
#
|
25
|
+
def signed?
|
26
|
+
!envelope.signature.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# The protobuffer serialized form of the envelope payload.
|
31
|
+
#
|
32
|
+
# @return [String] serialized payload
|
33
|
+
#
|
34
|
+
def payload_bytes
|
35
|
+
envelope.payload
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# The digest of the payload.
|
40
|
+
#
|
41
|
+
# @return [String] payload digest
|
42
|
+
#
|
43
|
+
def payload_digest
|
44
|
+
Fabric.crypto_suite.digest(envelope.payload)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Sets the envelope signature.
|
49
|
+
#
|
50
|
+
# @param [String] signature
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
#
|
54
|
+
def signature=(signature)
|
55
|
+
envelope.signature = signature
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Returns the results from the transaction result payload.
|
60
|
+
#
|
61
|
+
# @return [Payload] transaction result payload
|
62
|
+
#
|
63
|
+
def result
|
64
|
+
@result ||= parse_result_from_payload
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Returns the deserialized payload.
|
69
|
+
#
|
70
|
+
# @return [Common::Payload] Envelope payload
|
71
|
+
#
|
72
|
+
def payload
|
73
|
+
@payload ||= Common::Payload.decode(envelope.payload)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Returns the envelope payload header.
|
78
|
+
#
|
79
|
+
# Envelope => Payload => Header
|
80
|
+
#
|
81
|
+
# @return [Common::Header] Envelope Payload Header
|
82
|
+
#
|
83
|
+
def header
|
84
|
+
raise Fabric::Error, 'Missing header' if payload.header.nil?
|
85
|
+
|
86
|
+
@header ||= payload.header
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Returns the deserialized transaction channel header
|
91
|
+
#
|
92
|
+
# Envelope => Payload => Header => ChannelHeader
|
93
|
+
#
|
94
|
+
# @return [Common::ChannelHeader] envelop payload header channel header
|
95
|
+
#
|
96
|
+
def channel_header
|
97
|
+
@channel_header ||= Common::ChannelHeader.decode(header.channel_header)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Grabs the channel_name frmo the depths of the envelope.
|
102
|
+
#
|
103
|
+
# @return [String] channel name
|
104
|
+
#
|
105
|
+
def channel_name
|
106
|
+
channel_header.channel_id
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Returns the deserialized transaction
|
111
|
+
#
|
112
|
+
# @return [Protos::Transaction] transaction
|
113
|
+
#
|
114
|
+
def transaction
|
115
|
+
@transaction ||= Protos::Transaction.decode(payload.data)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
#
|
121
|
+
# Parse the transaction actions from the payload looking for the transaction result payload.
|
122
|
+
#
|
123
|
+
# @return [String] result payload
|
124
|
+
# @raise [Fabric::Error] if the transaction result payload is not found
|
125
|
+
#
|
126
|
+
def parse_result_from_payload
|
127
|
+
errors = []
|
128
|
+
transaction.actions.each do |action|
|
129
|
+
return parse_result_from_transaction_action(action)
|
130
|
+
rescue Fabric::Error => e
|
131
|
+
errors << e
|
132
|
+
end
|
133
|
+
|
134
|
+
raise Fabric::Error, "No proposal response found: #{errors.inspect}"
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Parse a single transaction action looking for the transaction result payload.
|
139
|
+
#
|
140
|
+
# @param [Protos::TransactionAction] transaction_action
|
141
|
+
#
|
142
|
+
# @return [Payload] transaction result payload
|
143
|
+
# @raise [Fabric::Error] if the endorsed_action is missing or the chaincode response is missing
|
144
|
+
#
|
145
|
+
def parse_result_from_transaction_action(transaction_action)
|
146
|
+
action_payload = Protos::ChaincodeActionPayload.decode(transaction_action.payload)
|
147
|
+
endorsed_action = action_payload.action
|
148
|
+
raise Fabric::Error, 'Missing endorsed action' if endorsed_action.nil?
|
149
|
+
|
150
|
+
response_payload = Protos::ProposalResponsePayload.decode(endorsed_action.proposal_response_payload)
|
151
|
+
chaincode_action = Protos::ChaincodeAction.decode(response_payload.extension)
|
152
|
+
chaincode_response = chaincode_action.response
|
153
|
+
raise Fabric::Error, 'Missing chaincode response' if chaincode_response.nil?
|
154
|
+
|
155
|
+
chaincode_response.payload
|
156
|
+
end
|
157
|
+
end
|
158
|
+
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 [Fabric::Client] client
|
80
|
+
#
|
81
|
+
# @return [Fabric::Gateway] gateway
|
82
|
+
#
|
83
|
+
def new_gateway(client)
|
84
|
+
Fabric::Gateway.new(self, client)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|