sidetree 0.1.0 → 0.1.3
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/.github/workflows/main.yml +1 -1
- data/lib/sidetree/cas/fetch_result.rb +20 -0
- data/lib/sidetree/cas/ipfs.rb +101 -0
- data/lib/sidetree/cas.rb +6 -0
- data/lib/sidetree/did.rb +12 -14
- data/lib/sidetree/key.rb +53 -35
- data/lib/sidetree/model/cas_file_base.rb +40 -0
- data/lib/sidetree/model/chunk.rb +21 -0
- data/lib/sidetree/model/chunk_file.rb +72 -0
- data/lib/sidetree/model/core_index_file.rb +197 -0
- data/lib/sidetree/model/core_proof_file.rb +102 -0
- data/lib/sidetree/model/delta.rb +10 -1
- data/lib/sidetree/model/document.rb +10 -4
- data/lib/sidetree/model/provisional_index_file.rb +129 -0
- data/lib/sidetree/model/provisional_proof_file.rb +80 -0
- data/lib/sidetree/model/service.rb +6 -6
- data/lib/sidetree/model/suffix.rb +2 -2
- data/lib/sidetree/model.rb +11 -4
- data/lib/sidetree/op/create.rb +56 -5
- data/lib/sidetree/op/deactivate.rb +82 -0
- data/lib/sidetree/op/recover.rb +19 -0
- data/lib/sidetree/op/updatable.rb +98 -0
- data/lib/sidetree/op/update.rb +19 -0
- data/lib/sidetree/op.rb +20 -16
- data/lib/sidetree/util/anchored_data_serializer.rb +41 -0
- data/lib/sidetree/util/compressor.rb +38 -0
- data/lib/sidetree/util/jwk.rb +47 -0
- data/lib/sidetree/util/jws.rb +52 -0
- data/lib/sidetree/util.rb +8 -0
- data/lib/sidetree/validator.rb +51 -29
- data/lib/sidetree/version.rb +1 -1
- data/lib/sidetree.rb +36 -19
- data/sidetree.gemspec +3 -3
- metadata +37 -20
- data/exe/ion +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b65f50d961dcfc15f1440ae26225fad38a7b42f43874764005b9d01cc624bb3a
|
4
|
+
data.tar.gz: dbe04b5d7072da5b1cbec36b90089d29cb69e6077cc60a501045d0eddff38d92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7aa774c61569b33aa98de41aa6ab265c58107346704aaebf673e45bc464f250f1073ea70994ae1a884cbd92964dc7e84b1841e2cbfc917601157a2eea1a7854
|
7
|
+
data.tar.gz: a68ccd2615046db978b08dcffa585611f2ed44ee59a765e0c53614074892ad83e5f733df493bc8f06a15a4b264c777c52867f486562e13918b5cf4fff236c758
|
data/.github/workflows/main.yml
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module CAS
|
3
|
+
class FetchResult
|
4
|
+
CODE_CAS_NOT_REACHABLE = "cas_not_reachable"
|
5
|
+
CODE_INVALID_HASH = "content_hash_invalid"
|
6
|
+
CODE_MAX_SIZE_EXCEEDED = "content_exceeds_maximum_allowed_size"
|
7
|
+
CODE_NOT_FILE = "content_not_a_file"
|
8
|
+
CODE_NOT_FOUND = "content_not_found"
|
9
|
+
CODE_SUCCESS = "success"
|
10
|
+
|
11
|
+
attr_reader :code
|
12
|
+
attr_reader :content
|
13
|
+
|
14
|
+
def initialize(code, content = nil)
|
15
|
+
@code = code
|
16
|
+
@content = content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "uri"
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
module Sidetree
|
6
|
+
module CAS
|
7
|
+
class IPFS
|
8
|
+
attr_reader :base_url
|
9
|
+
attr_reader :fetch_timeout
|
10
|
+
|
11
|
+
# @raise [Sidetree::Error]
|
12
|
+
def initialize(
|
13
|
+
schema: "http",
|
14
|
+
host: "localhost",
|
15
|
+
port: 5001,
|
16
|
+
base_path: "/api/v0",
|
17
|
+
fetch_timeout: nil
|
18
|
+
)
|
19
|
+
@base_url = "#{schema}://#{host}:#{port}#{base_path}"
|
20
|
+
@fetch_timeout = fetch_timeout
|
21
|
+
raise Sidetree::Error, "Failed to connect to IPFS endpoint." unless up?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Writes the given content to CAS.
|
25
|
+
# @param [String] content content to be stored.
|
26
|
+
# @return [String] SHA256 hash in base64url encoding which represents the address of the content.
|
27
|
+
# @raise [Sidetree::Error] If IPFS write fails
|
28
|
+
def write(content)
|
29
|
+
multipart_boundary = SecureRandom.hex(32)
|
30
|
+
uri = URI("#{base_url}/add")
|
31
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
32
|
+
http.use_ssl = uri.scheme == "https"
|
33
|
+
req = Net::HTTP::Post.new(uri)
|
34
|
+
req[
|
35
|
+
"Content-Type"
|
36
|
+
] = "multipart/form-data; boundary=#{multipart_boundary}"
|
37
|
+
req["Content-Disposition"] = 'form-data; name=; filename=""'
|
38
|
+
req.body = build_body(multipart_boundary, content)
|
39
|
+
res = http.request(req)
|
40
|
+
if res.is_a?(Net::HTTPSuccess)
|
41
|
+
results = JSON.parse(res.body)
|
42
|
+
results["Hash"]
|
43
|
+
else
|
44
|
+
raise Sidetree::Error, "Failed writing content. #{res.body}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get content from IPFS.
|
49
|
+
# @param [String] addr cas uri.
|
50
|
+
# @return [Sidetree::CAS::FetchResult] Fetch result containing the content if found.
|
51
|
+
# The result code is set to FetchResultCode.MaxSizeExceeded if the content exceeds the +max_bytesize+.
|
52
|
+
def read(addr)
|
53
|
+
fetch_url = "#{base_url}/cat?arg=#{addr}"
|
54
|
+
begin
|
55
|
+
res = Net::HTTP.post_form(URI(fetch_url), {})
|
56
|
+
if res.is_a?(Net::HTTPSuccess)
|
57
|
+
FetchResult.new(FetchResult::CODE_SUCCESS, res.body)
|
58
|
+
else
|
59
|
+
FetchResult.new(FetchResult::CODE_NOT_FOUND)
|
60
|
+
end
|
61
|
+
rescue Errno::ECONNREFUSED
|
62
|
+
FetchResult.new(FetchResult::CODE_CAS_NOT_REACHABLE)
|
63
|
+
rescue StandardError
|
64
|
+
FetchResult.new(FetchResult::CODE_NOT_FOUND)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get node information from IPFS endpoint.
|
69
|
+
# @return [String] node information.
|
70
|
+
def id
|
71
|
+
res = Net::HTTP.post_form(URI("#{base_url}/id"), {})
|
72
|
+
res.body if res.is_a?(Net::HTTPSuccess)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check IPFS endpoint are up and running.
|
76
|
+
# @return [Boolean]
|
77
|
+
def up?
|
78
|
+
begin
|
79
|
+
id
|
80
|
+
true
|
81
|
+
rescue Errno::ECONNREFUSED
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Fetch content from IPFS.
|
89
|
+
def fetch(uri, max_bytesize: nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_body(boundary, content)
|
93
|
+
begin_boundary = "--#{boundary}\n"
|
94
|
+
first_part_content_type = "Content-Disposition: form-data;\n"
|
95
|
+
first_part_content_type += "Content-Type: application/octet-stream\n\n"
|
96
|
+
end_boundary = "\n--#{boundary}--"
|
97
|
+
begin_boundary + first_part_content_type + content + end_boundary
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/sidetree/cas.rb
ADDED
data/lib/sidetree/did.rb
CHANGED
@@ -6,21 +6,21 @@ module Sidetree
|
|
6
6
|
|
7
7
|
# @raise [Sidetree::Error]
|
8
8
|
def initialize(did)
|
9
|
-
if !did.start_with?(
|
10
|
-
raise Error,
|
9
|
+
if !did.start_with?("did:ion:") && !did.start_with?("did:sidetree:")
|
10
|
+
raise Error, "Expected DID method not given in DID."
|
11
11
|
end
|
12
|
-
if did.count(
|
13
|
-
raise Error,
|
12
|
+
if did.count(":") > (Sidetree::Params.testnet? ? 4 : 3)
|
13
|
+
raise Error, "Unsupported DID format."
|
14
14
|
end
|
15
15
|
if Sidetree::Params.testnet?
|
16
|
-
_, @method, _, @suffix, @long_suffix = did.split(
|
16
|
+
_, @method, _, @suffix, @long_suffix = did.split(":")
|
17
17
|
else
|
18
|
-
_, @method, @suffix, @long_suffix = did.split(
|
18
|
+
_, @method, @suffix, @long_suffix = did.split(":")
|
19
19
|
end
|
20
20
|
|
21
21
|
if @long_suffix
|
22
22
|
unless suffix == create_op.suffix.unique_suffix
|
23
|
-
raise Error,
|
23
|
+
raise Error, "DID document mismatches short-form DID."
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -39,19 +39,17 @@ module Sidetree
|
|
39
39
|
method: Sidetree::Params::DEFAULT_METHOD
|
40
40
|
)
|
41
41
|
unless document.is_a?(Sidetree::Model::Document)
|
42
|
-
raise Error,
|
42
|
+
raise Error, "document must be Sidetree::Model::Document instance."
|
43
43
|
end
|
44
44
|
unless update_key.is_a?(Sidetree::Key)
|
45
|
-
raise Error,
|
45
|
+
raise Error, "update_key must be Sidetree::Key instance."
|
46
46
|
end
|
47
47
|
unless recovery_key.is_a?(Sidetree::Key)
|
48
|
-
raise Error,
|
48
|
+
raise Error, "recovery_key must be Sidetree::Key instance."
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
]
|
54
|
-
delta = Model::Delta.new(patches, update_key.to_commitment)
|
51
|
+
delta =
|
52
|
+
Model::Delta.new([document.to_replace_patch], update_key.to_commitment)
|
55
53
|
suffix =
|
56
54
|
Sidetree::Model::Suffix.new(delta.to_hash, recovery_key.to_commitment)
|
57
55
|
DID.new(
|
data/lib/sidetree/key.rb
CHANGED
@@ -12,18 +12,18 @@ module Sidetree
|
|
12
12
|
public_key: nil,
|
13
13
|
id: nil,
|
14
14
|
purposes: [],
|
15
|
-
type:
|
15
|
+
type: Sidetree::Params::DEFAULT_PUBKEY_TYPE
|
16
16
|
)
|
17
17
|
if private_key
|
18
18
|
unless Key.valid_private_key?(private_key)
|
19
|
-
raise Error,
|
19
|
+
raise Error, "private key is invalid range."
|
20
20
|
end
|
21
21
|
|
22
22
|
@private_key = private_key
|
23
23
|
pub = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_key)
|
24
24
|
if public_key
|
25
25
|
unless pub == public_key
|
26
|
-
raise Error,
|
26
|
+
raise Error, "Public and private keys do not match."
|
27
27
|
end
|
28
28
|
else
|
29
29
|
public_key = pub
|
@@ -31,13 +31,13 @@ module Sidetree
|
|
31
31
|
end
|
32
32
|
|
33
33
|
unless public_key
|
34
|
-
raise Error,
|
34
|
+
raise Error, "Specify either the private key or the public key"
|
35
35
|
end
|
36
36
|
unless public_key.is_a?(ECDSA::Point)
|
37
|
-
raise Error,
|
37
|
+
raise Error, "public key must be an ECDSA::Point instance."
|
38
38
|
end
|
39
39
|
unless ECDSA::Group::Secp256k1.valid_public_key?(public_key)
|
40
|
-
raise Error,
|
40
|
+
raise Error, "public key is invalid."
|
41
41
|
end
|
42
42
|
|
43
43
|
@public_key = public_key
|
@@ -56,62 +56,67 @@ module Sidetree
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# Generate Secp256k1 key.
|
59
|
-
# @
|
60
|
-
# @
|
59
|
+
# @param [String] id Public key ID.
|
60
|
+
# @param [String] purpose Purpose for public key. Supported values defined by [Sidetree::PublicKeyPurpose].
|
61
|
+
# @param [String] type The type of public key defined by https://w3c-ccg.github.io/ld-cryptosuite-registry/.
|
61
62
|
# @return [Sidetree::Key]
|
62
63
|
# @raise [Sidetree::Error]
|
63
|
-
def self.generate(
|
64
|
+
def self.generate(
|
65
|
+
id: nil,
|
66
|
+
purposes: [],
|
67
|
+
type: Sidetree::Params::DEFAULT_PUBKEY_TYPE
|
68
|
+
)
|
64
69
|
private_key =
|
65
70
|
1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1)
|
66
|
-
Key.new(private_key: private_key, purposes: purposes, id: id)
|
71
|
+
Key.new(private_key: private_key, purposes: purposes, id: id, type: type)
|
67
72
|
end
|
68
73
|
|
69
74
|
# Generate key instance from jwk Hash.
|
70
75
|
# @param [Hash] data jwk Hash object.
|
71
76
|
# @return [Sidetree::Key]
|
72
77
|
# @raise [Sidetree::Error]
|
73
|
-
def self.
|
74
|
-
key_data = data[
|
75
|
-
key_type = key_data[
|
76
|
-
curve = key_data[
|
77
|
-
if key_type.nil? || key_type !=
|
78
|
+
def self.from_jwk(data)
|
79
|
+
key_data = data["publicKeyJwk"] ? data["publicKeyJwk"] : data
|
80
|
+
key_type = key_data["kty"]
|
81
|
+
curve = key_data["crv"]
|
82
|
+
if key_type.nil? || key_type != "EC"
|
78
83
|
raise Error, "Unsupported key type '#{key_type}' specified."
|
79
84
|
end
|
80
|
-
if curve.nil? || curve !=
|
85
|
+
if curve.nil? || curve != "secp256k1"
|
81
86
|
raise Error, "Unsupported curve '#{curve}' specified."
|
82
87
|
end
|
83
|
-
raise Error,
|
84
|
-
raise Error,
|
88
|
+
raise Error, "x property required." unless key_data["x"]
|
89
|
+
raise Error, "y property required." unless key_data["y"]
|
85
90
|
|
86
91
|
# `x` and `y` need 43 Base64URL encoded bytes to contain 256 bits.
|
87
|
-
unless key_data[
|
92
|
+
unless key_data["x"].length == 43
|
88
93
|
raise Error, "Secp256k1 JWK 'x' property must be 43 bytes."
|
89
94
|
end
|
90
|
-
unless key_data[
|
95
|
+
unless key_data["y"].length == 43
|
91
96
|
raise Error, "Secp256k1 JWK 'y' property must be 43 bytes."
|
92
97
|
end
|
93
98
|
|
94
|
-
x = Base64.urlsafe_decode64(key_data[
|
95
|
-
y = Base64.urlsafe_decode64(key_data[
|
99
|
+
x = Base64.urlsafe_decode64(key_data["x"])
|
100
|
+
y = Base64.urlsafe_decode64(key_data["y"])
|
96
101
|
point =
|
97
102
|
ECDSA::Format::PointOctetString.decode(
|
98
|
-
[
|
103
|
+
["04"].pack("H*") + x + y,
|
99
104
|
ECDSA::Group::Secp256k1
|
100
105
|
)
|
101
106
|
private_key =
|
102
|
-
if key_data[
|
103
|
-
Base64.urlsafe_decode64(key_data[
|
107
|
+
if key_data["d"]
|
108
|
+
Base64.urlsafe_decode64(key_data["d"]).unpack1("H*").to_i(16)
|
104
109
|
else
|
105
110
|
nil
|
106
111
|
end
|
107
112
|
|
108
|
-
purposes = data[
|
113
|
+
purposes = data["purposes"] ? data["purposes"] : []
|
109
114
|
Key.new(
|
110
115
|
public_key: point,
|
111
116
|
private_key: private_key,
|
112
117
|
purposes: purposes,
|
113
|
-
id: data[
|
114
|
-
type: data[
|
118
|
+
id: data["id"],
|
119
|
+
type: data["type"]
|
115
120
|
)
|
116
121
|
end
|
117
122
|
|
@@ -123,12 +128,13 @@ module Sidetree
|
|
123
128
|
end
|
124
129
|
|
125
130
|
# Generate JSON::JWK object.
|
131
|
+
# @param [Boolean] include_privkey whether include private key or not.
|
126
132
|
# @return [JSON::JWK]
|
127
|
-
def to_jwk
|
133
|
+
def to_jwk(include_privkey: false)
|
128
134
|
jwk =
|
129
|
-
|
130
|
-
kty:
|
131
|
-
crv:
|
135
|
+
Sidetree::Util::JWK.parse(
|
136
|
+
kty: "EC",
|
137
|
+
crv: "secp256k1",
|
132
138
|
x:
|
133
139
|
Base64.urlsafe_encode64(
|
134
140
|
ECDSA::Format::FieldElementOctetString.encode(
|
@@ -146,18 +152,30 @@ module Sidetree
|
|
146
152
|
padding: false
|
147
153
|
)
|
148
154
|
)
|
149
|
-
jwk[
|
155
|
+
jwk["d"] = encoded_private_key if include_privkey && private_key
|
150
156
|
jwk
|
151
157
|
end
|
152
158
|
|
159
|
+
# Convert the private key to the format (OpenSSL::PKey::EC) in which it will be signed in JWS.
|
160
|
+
# @return [OpenSSL::PKey::EC]
|
161
|
+
def jws_sign_key
|
162
|
+
return nil unless private_key
|
163
|
+
to_jwk(include_privkey: true).to_key
|
164
|
+
end
|
165
|
+
|
153
166
|
# Generate commitment for this key.
|
154
167
|
# @return [String] Base64 encoded commitment.
|
155
168
|
def to_commitment
|
156
169
|
digest = Digest::SHA256.digest(to_jwk.normalize.to_json_c14n)
|
157
|
-
|
158
170
|
Sidetree.to_hash(digest)
|
159
171
|
end
|
160
172
|
|
173
|
+
# Generate reveal value for this key.
|
174
|
+
# @return [String] Base64 encoded reveal value.
|
175
|
+
def to_reveal_value
|
176
|
+
Sidetree.to_hash(to_jwk.normalize.to_json_c14n)
|
177
|
+
end
|
178
|
+
|
161
179
|
def to_h
|
162
180
|
h = { publicKeyJwk: to_jwk.normalize, purposes: purposes }
|
163
181
|
h[:id] = id if id
|
@@ -170,7 +188,7 @@ module Sidetree
|
|
170
188
|
def encoded_private_key
|
171
189
|
if private_key
|
172
190
|
Base64.urlsafe_encode64(
|
173
|
-
[private_key.to_s(16).rjust(32 * 2,
|
191
|
+
[private_key.to_s(16).rjust(32 * 2, "0")].pack("H*"),
|
174
192
|
padding: false
|
175
193
|
)
|
176
194
|
else
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
class CASFileBase
|
4
|
+
include Sidetree::Util::Compressor
|
5
|
+
|
6
|
+
# Decompress +data+.
|
7
|
+
# @param [String] data compressed data.
|
8
|
+
# @param [Integer] max_size
|
9
|
+
# @return [String] decompressed data.
|
10
|
+
# @raise [Sidetree::Error]
|
11
|
+
def self.decompress(data, max_size)
|
12
|
+
begin
|
13
|
+
Sidetree::Util::Compressor.decompress(
|
14
|
+
data,
|
15
|
+
max_bytes:
|
16
|
+
max_size *
|
17
|
+
Sidetree::Util::Compressor::ESTIMATE_DECOMPRESSION_MULTIPLIER
|
18
|
+
)
|
19
|
+
rescue Zlib::GzipFile::Error
|
20
|
+
raise Sidetree::Error,
|
21
|
+
"#{self.name.split("::").last.split(/(?=[A-Z])/).join(" ")} decompression failure"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Build json string to be stored in CAS.
|
26
|
+
# Child classes must implement this method.
|
27
|
+
# @return [String]
|
28
|
+
def to_json
|
29
|
+
raise NotImplementedError,
|
30
|
+
"You must implement #{self.class}##{__method__}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generate compressed data via to_json to be stored in CAS.
|
34
|
+
# @return [String] compressed data.
|
35
|
+
def to_compress
|
36
|
+
compress(to_json)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
class Chunk
|
4
|
+
attr_reader :chunk_file_uri
|
5
|
+
|
6
|
+
# Initializer
|
7
|
+
# @param [String] chunk_file_uri chunk file uri.
|
8
|
+
# @raise [Sidetree::Error]
|
9
|
+
def initialize(chunk_file_uri)
|
10
|
+
unless chunk_file_uri.is_a?(String)
|
11
|
+
raise Sidetree::Error, "chunk_file_uri must be String"
|
12
|
+
end
|
13
|
+
@chunk_file_uri = chunk_file_uri
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{ chunkFileUri: chunk_file_uri }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
# https://identity.foundation/sidetree/spec/#chunk-files
|
4
|
+
class ChunkFile < CASFileBase
|
5
|
+
attr_reader :deltas # Array of Sidetree::Model::Delta
|
6
|
+
|
7
|
+
def initialize(deltas = [])
|
8
|
+
deltas.each do |delta|
|
9
|
+
unless delta.is_a?(Sidetree::Model::Delta)
|
10
|
+
raise Sidetree::Error,
|
11
|
+
"deltas contains data that is not Sidetree::Model::Delta object."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
@deltas = deltas
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generate chunk file from operations.
|
18
|
+
# @param [Array[Sidetree::OP::Create]] create_ops
|
19
|
+
# @param [Array[Sidetree::OP::Recover]] recover_ops
|
20
|
+
# @param [Array[Sidetree::OP::Update]] update_ops
|
21
|
+
def self.create_from_ops(create_ops: [], recover_ops: [], update_ops: [])
|
22
|
+
deltas = (create_ops + recover_ops + update_ops).map(&:delta)
|
23
|
+
ChunkFile.new(deltas)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse chunk file from compressed data.
|
27
|
+
# @param [String] chunk_file compressed chunk file data.
|
28
|
+
# @param [Boolean] compressed Whether the chunk_file is compressed or not, default: true.
|
29
|
+
# @return [Sidetree::Model::ChunkFile]
|
30
|
+
# @raise [Sidetree::Error]
|
31
|
+
def self.parse(chunk_file, compressed: true)
|
32
|
+
decompressed =
|
33
|
+
(
|
34
|
+
if compressed
|
35
|
+
decompress(chunk_file, Sidetree::Params::MAX_CHUNK_FILE_SIZE)
|
36
|
+
else
|
37
|
+
chunk_file
|
38
|
+
end
|
39
|
+
)
|
40
|
+
json = JSON.parse(decompressed, symbolize_names: true)
|
41
|
+
json.keys.each do |k|
|
42
|
+
unless k == :deltas
|
43
|
+
raise Sidetree::Error,
|
44
|
+
"Unexpected property #{k.to_s} in chunk file."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
unless json[:deltas].is_a?(Array)
|
48
|
+
raise Sidetree::Error,
|
49
|
+
"Invalid chunk file, deltas property is not an array."
|
50
|
+
end
|
51
|
+
ChunkFile.new(
|
52
|
+
json[:deltas].map do |delta|
|
53
|
+
Sidetree::Model::Delta.from_object(delta)
|
54
|
+
end
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Build json string to be stored in CAS.
|
59
|
+
# @return [String] json string.
|
60
|
+
def to_json
|
61
|
+
{ deltas: deltas.map(&:to_h) }.to_json
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if the +other+ object have the same chunk data.
|
65
|
+
# @return [Boolean]
|
66
|
+
def ==(other)
|
67
|
+
return false unless other.is_a?(ChunkFile)
|
68
|
+
deltas == other.deltas
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|