sidetree 0.1.1 → 0.1.2
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/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/key.rb +11 -6
- data/lib/sidetree/model/chunk.rb +21 -0
- data/lib/sidetree/model/chunk_file.rb +80 -0
- data/lib/sidetree/model/core_index_file.rb +204 -0
- data/lib/sidetree/model/delta.rb +10 -1
- data/lib/sidetree/model/provisional_index_file.rb +134 -0
- data/lib/sidetree/model/suffix.rb +2 -2
- data/lib/sidetree/model.rb +4 -0
- data/lib/sidetree/op/create.rb +36 -3
- data/lib/sidetree/op/deactivate.rb +70 -1
- data/lib/sidetree/op/recover.rb +11 -2
- data/lib/sidetree/op/updatable.rb +84 -0
- data/lib/sidetree/op/update.rb +19 -0
- data/lib/sidetree/op.rb +2 -0
- data/lib/sidetree/util/anchored_data_serializer.rb +41 -0
- data/lib/sidetree/util/compressor.rb +38 -0
- data/lib/sidetree/util.rb +6 -0
- data/lib/sidetree/validator.rb +23 -0
- data/lib/sidetree/version.rb +1 -1
- data/lib/sidetree.rb +16 -1
- data/sidetree.gemspec +2 -1
- metadata +30 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c2645dbb06ae761fe9e966134e7b824a2d3832f9972cd0808f92d46b3f6056e
|
4
|
+
data.tar.gz: bce1dce70b331c7b7093ceffa8c1af4e6953914f12aae2543cf6bcc9839ba97e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff5f5050ec0097e8a6f9a46d7f56c1bf43c08844d0b74a438d9daf07e508b67868537b8ab788658894a9b086673e697cab9aa9d8872c0437a61c169081986719
|
7
|
+
data.tar.gz: 7252b89b1c99ee2a5cc4bcb15a6f10cf08c556881665b0d5132a2aafbb414d9a51e0aafc8180b4dd3dc2e96efb40f01d9334ae5dcc493043a318002bc4b3e699
|
@@ -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/key.rb
CHANGED
@@ -12,7 +12,7 @@ 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)
|
@@ -56,21 +56,26 @@ 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.
|
78
|
+
def self.from_jwk(data)
|
74
79
|
key_data = data["publicKeyJwk"] ? data["publicKeyJwk"] : data
|
75
80
|
key_type = key_data["kty"]
|
76
81
|
curve = key_data["crv"]
|
@@ -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,80 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
# https://identity.foundation/sidetree/spec/#chunk-files
|
4
|
+
class ChunkFile
|
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.map(&:delta)
|
23
|
+
# TODO add update and recover operation delta
|
24
|
+
ChunkFile.new(deltas)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parse chunk file from compressed data.
|
28
|
+
# @param [String] chunk_file compressed chunk file data.
|
29
|
+
# @param [Boolean] compressed Whether the chunk_file is compressed or not, default: true.
|
30
|
+
# @return [Sidetree::Model::ChunkFile]
|
31
|
+
# @raise [Sidetree::Error]
|
32
|
+
def self.parse(chunk_file, compressed: true)
|
33
|
+
max_bytes =
|
34
|
+
Sidetree::Params::MAX_CHUNK_FILE_SIZE *
|
35
|
+
Sidetree::Util::Compressor::ESTIMATE_DECOMPRESSION_MULTIPLIER
|
36
|
+
decompressed =
|
37
|
+
(
|
38
|
+
if compressed
|
39
|
+
Sidetree::Util::Compressor.decompress(
|
40
|
+
chunk_file,
|
41
|
+
max_bytes: max_bytes
|
42
|
+
)
|
43
|
+
else
|
44
|
+
chunk_file
|
45
|
+
end
|
46
|
+
)
|
47
|
+
json = JSON.parse(decompressed, symbolize_names: true)
|
48
|
+
json.keys.each do |k|
|
49
|
+
unless k == :deltas
|
50
|
+
raise Sidetree::Error,
|
51
|
+
"Unexpected property #{k.to_s} in chunk file."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
unless json[:deltas].is_a?(Array)
|
55
|
+
raise Sidetree::Error,
|
56
|
+
"Invalid chunk file, deltas property is not an array."
|
57
|
+
end
|
58
|
+
ChunkFile.new(
|
59
|
+
json[:deltas].map do |delta|
|
60
|
+
Sidetree::Model::Delta.from_object(delta)
|
61
|
+
end
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Compress this chunk file
|
66
|
+
# @return [String] compressed data.
|
67
|
+
def to_compress
|
68
|
+
params = { deltas: deltas.map(&:to_h) }
|
69
|
+
Sidetree::Util::Compressor.compress(params.to_json)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check if the +other+ object have the same chunk data.
|
73
|
+
# @return [Boolean]
|
74
|
+
def ==(other)
|
75
|
+
return false unless other.is_a?(ChunkFile)
|
76
|
+
deltas == other.deltas
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
# https://identity.foundation/sidetree/spec/#core-index-file
|
4
|
+
class CoreIndexFile
|
5
|
+
attr_reader :core_proof_file_uri
|
6
|
+
attr_reader :provisional_index_file_uri
|
7
|
+
attr_reader :writer_lock_id
|
8
|
+
attr_reader :create_ops
|
9
|
+
attr_reader :recover_ops
|
10
|
+
attr_reader :deactivate_ops
|
11
|
+
|
12
|
+
# @param [Array[Sidetree::OP::Create]] create_ops
|
13
|
+
# @param [Array[Sidetree::OP::Recover]] recover_ops
|
14
|
+
# @param [Array[Sidetree::OP::Deactivate]] deactivate_ops
|
15
|
+
# @param [String] provisional_index_file_uri
|
16
|
+
# @param [String] core_proof_file_uri
|
17
|
+
# @raise [Sidetree::Error]
|
18
|
+
def initialize(
|
19
|
+
create_ops: [],
|
20
|
+
recover_ops: [],
|
21
|
+
deactivate_ops: [],
|
22
|
+
provisional_index_file_uri: nil,
|
23
|
+
core_proof_file_uri: nil,
|
24
|
+
writer_lock_id: nil
|
25
|
+
)
|
26
|
+
(create_ops + recover_ops + deactivate_ops).each do |operation|
|
27
|
+
unless operation.is_a?(Sidetree::OP::Base)
|
28
|
+
raise Sidetree::Error, "Invalid operation class specified."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@create_ops = create_ops
|
32
|
+
@recover_ops = recover_ops
|
33
|
+
@deactivate_ops = deactivate_ops
|
34
|
+
@provisional_index_file_uri = provisional_index_file_uri
|
35
|
+
@core_proof_file_uri = core_proof_file_uri
|
36
|
+
@writer_lock_id = writer_lock_id
|
37
|
+
unless did_suffixes.length == did_suffixes.uniq.length
|
38
|
+
raise Sidetree::Error,
|
39
|
+
"Core index file multiple operations for the same DID"
|
40
|
+
end
|
41
|
+
if writer_lock_id &&
|
42
|
+
writer_lock_id.bytesize > Sidetree::Params::MAX_WRITER_LOCK_ID_SIZE
|
43
|
+
raise Sidetree::Error,
|
44
|
+
"Writer lock ID of #{writer_lock_id.bytesize} bytes exceeded the maximum size of #{Sidetree::Params::MAX_WRITER_LOCK_ID_SIZE} bytes"
|
45
|
+
end
|
46
|
+
if provisional_index_file_uri
|
47
|
+
Validator.validate_cas_file_uri!(
|
48
|
+
provisional_index_file_uri,
|
49
|
+
"provisional index file URI"
|
50
|
+
)
|
51
|
+
else
|
52
|
+
if (create_ops.length + recover_ops.length) > 0
|
53
|
+
raise Sidetree::Error, "Provisional index file uri missing"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if recover_ops.length > 0 || deactivate_ops.length > 0
|
57
|
+
Validator.validate_cas_file_uri!(
|
58
|
+
core_proof_file_uri,
|
59
|
+
"core proof file URI"
|
60
|
+
)
|
61
|
+
else
|
62
|
+
if core_proof_file_uri
|
63
|
+
raise Sidetree::Error,
|
64
|
+
"Core proof file is specified but there is no recover and no deactivate operation"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse core index file.
|
70
|
+
# @param [String] index_data core index file data.
|
71
|
+
# @param [Boolean] compressed Whether the index_data is compressed or not, default: true.
|
72
|
+
# @return [Sidetree::Model::ProvisionalIndexFile]
|
73
|
+
def self.parse(index_data, compressed: true)
|
74
|
+
decompressed =
|
75
|
+
(
|
76
|
+
if compressed
|
77
|
+
begin
|
78
|
+
Sidetree::Util::Compressor.decompress(
|
79
|
+
index_data,
|
80
|
+
max_bytes: Sidetree::Params::MAX_CORE_INDEX_FILE_SIZE
|
81
|
+
)
|
82
|
+
rescue Zlib::GzipFile::Error
|
83
|
+
raise Sidetree::Error, "Core index file decompression failure"
|
84
|
+
end
|
85
|
+
else
|
86
|
+
index_data
|
87
|
+
end
|
88
|
+
)
|
89
|
+
begin
|
90
|
+
json = JSON.parse(decompressed, symbolize_names: true)
|
91
|
+
create_ops, recover_ops, deactivate_ops = [], [], []
|
92
|
+
core_proof_uri, provisional_index_uri = nil, nil
|
93
|
+
json.each do |k, v|
|
94
|
+
case k
|
95
|
+
when :provisionalIndexFileUri
|
96
|
+
provisional_index_uri = v
|
97
|
+
when :coreProofFileUri
|
98
|
+
core_proof_uri = v
|
99
|
+
when :operations
|
100
|
+
create_ops, recover_ops, deactivate_ops = parse_operations(v)
|
101
|
+
when :writerLockId
|
102
|
+
unless v.is_a?(String)
|
103
|
+
raise Sidetree::Error, "Core index file writerLockId not string"
|
104
|
+
end
|
105
|
+
else
|
106
|
+
raise Sidetree::Error,
|
107
|
+
"Unexpected property #{k.to_s} in core index file"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
CoreIndexFile.new(
|
111
|
+
create_ops: create_ops,
|
112
|
+
recover_ops: recover_ops,
|
113
|
+
deactivate_ops: deactivate_ops,
|
114
|
+
provisional_index_file_uri: provisional_index_uri,
|
115
|
+
core_proof_file_uri: core_proof_uri,
|
116
|
+
writer_lock_id: json[:writerLockId]
|
117
|
+
)
|
118
|
+
rescue JSON::ParserError
|
119
|
+
raise Sidetree::Error, "Core index file is not json"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.parse_operations(operations)
|
124
|
+
create_ops, recover_ops, deactivate_ops = [], [], []
|
125
|
+
operations.each do |o_k, o_v|
|
126
|
+
case o_k
|
127
|
+
when :create
|
128
|
+
unless o_v.is_a?(Array)
|
129
|
+
raise Sidetree::Error, "Core index file create property not array"
|
130
|
+
end
|
131
|
+
create_ops =
|
132
|
+
o_v.map do |create|
|
133
|
+
Sidetree::OP::Create.from_json(create.to_json_c14n)
|
134
|
+
end
|
135
|
+
when :recover
|
136
|
+
unless o_v.is_a?(Array)
|
137
|
+
raise Sidetree::Error,
|
138
|
+
"Core index file recover property not array"
|
139
|
+
end
|
140
|
+
recover_ops =
|
141
|
+
o_v.map do |recover|
|
142
|
+
Sidetree::OP::Recover.from_json(recover.to_json_c14n)
|
143
|
+
end
|
144
|
+
when :deactivate
|
145
|
+
unless o_v.is_a?(Array)
|
146
|
+
raise Sidetree::Error,
|
147
|
+
"Core index file deactivate property not array"
|
148
|
+
end
|
149
|
+
deactivate_ops =
|
150
|
+
o_v.map do |deactivate|
|
151
|
+
Sidetree::OP::Deactivate.from_json(deactivate.to_json_c14n)
|
152
|
+
end
|
153
|
+
else
|
154
|
+
raise Sidetree::Error,
|
155
|
+
"Unexpected property #{o_k.to_s} in operations property in core index file"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
[create_ops, recover_ops, deactivate_ops]
|
159
|
+
end
|
160
|
+
|
161
|
+
private_class_method :parse_operations
|
162
|
+
|
163
|
+
def did_suffixes
|
164
|
+
create_ops.map { |o| o.suffix.unique_suffix } +
|
165
|
+
(recover_ops + deactivate_ops).map { |o| o.did_suffix }
|
166
|
+
end
|
167
|
+
|
168
|
+
# Compress this core index file
|
169
|
+
# @return [String] compressed data.
|
170
|
+
def to_compress
|
171
|
+
params = {}
|
172
|
+
params[
|
173
|
+
:provisionalIndexFileUri
|
174
|
+
] = provisional_index_file_uri if provisional_index_file_uri
|
175
|
+
operations = {}
|
176
|
+
unless create_ops.empty?
|
177
|
+
operations[:create] = create_ops.map do |create|
|
178
|
+
{ suffixData: create.suffix.to_h }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
unless recover_ops.empty?
|
182
|
+
operations[:recover] = recover_ops.map do |recover|
|
183
|
+
{
|
184
|
+
didSuffix: recover.did_suffix,
|
185
|
+
revealValue: recover.revealed_value
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
unless deactivate_ops.empty?
|
190
|
+
operations[:deactivate] = deactivate_ops.map do |deactivate|
|
191
|
+
{
|
192
|
+
didSuffix: deactivate.did_suffix,
|
193
|
+
revealValue: deactivate.revealed_value
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
params[:operations] = operations
|
198
|
+
params[:coreProofFileUri] = core_proof_file_uri if core_proof_file_uri
|
199
|
+
params[:writerLockId] = writer_lock_id if writer_lock_id
|
200
|
+
Sidetree::Util::Compressor.compress(params.to_json)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
data/lib/sidetree/model/delta.rb
CHANGED
@@ -3,6 +3,7 @@ module Sidetree
|
|
3
3
|
class Delta
|
4
4
|
attr_reader :patches, :update_commitment
|
5
5
|
|
6
|
+
# Initializer
|
6
7
|
# @param [Array[Hash]] patches
|
7
8
|
# @param [String] update_commitment
|
8
9
|
# @raise [Sidetree::Error]
|
@@ -11,7 +12,10 @@ module Sidetree
|
|
11
12
|
@update_commitment = update_commitment
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
+
# Create delta object from hash object.
|
16
|
+
# @param [Hash] object
|
17
|
+
# @return [Sidetree::Model::Delta]
|
18
|
+
def self.from_object(object)
|
15
19
|
Sidetree::Validator.validate_delta!(object)
|
16
20
|
Delta.new(object[:patches], object[:updateCommitment])
|
17
21
|
end
|
@@ -23,6 +27,11 @@ module Sidetree
|
|
23
27
|
def to_hash
|
24
28
|
Sidetree.to_hash(to_h)
|
25
29
|
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
return false unless other.is_a?(Delta)
|
33
|
+
to_hash == other.to_hash
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Model
|
3
|
+
# https://identity.foundation/sidetree/spec/#provisional-index-file
|
4
|
+
class ProvisionalIndexFile
|
5
|
+
attr_reader :provisional_proof_file_uri
|
6
|
+
attr_reader :chunks
|
7
|
+
attr_reader :operations
|
8
|
+
|
9
|
+
# Initialize
|
10
|
+
# @param [String] proof_file_uri Provisional Proof File URI.
|
11
|
+
# @param [Array[Sidetree::Model::Chunk]] chunks
|
12
|
+
# @param [Array[Sidetree::OP::Update]] operations Update operations
|
13
|
+
# @raise [Sidetree::Error]
|
14
|
+
def initialize(proof_file_uri: nil, chunks: [], operations: [])
|
15
|
+
if !chunks.is_a?(Array) || chunks.empty?
|
16
|
+
raise Sidetree::Error,
|
17
|
+
"Provisional Index File chunk property missing or incorrect type"
|
18
|
+
elsif chunks.length > 1
|
19
|
+
raise Sidetree::Error,
|
20
|
+
"Provisional Index File chunks does not have exactly one element"
|
21
|
+
end
|
22
|
+
@provisional_proof_file_uri = proof_file_uri
|
23
|
+
@chunks = chunks
|
24
|
+
@operations = operations
|
25
|
+
did_suffixes = operations.map(&:did_suffix).compact
|
26
|
+
if did_suffixes.length > 0
|
27
|
+
unless did_suffixes.length == did_suffixes.uniq.length
|
28
|
+
raise Sidetree::Error,
|
29
|
+
"Provisional Index File has multiple operations for same DID"
|
30
|
+
end
|
31
|
+
else
|
32
|
+
if proof_file_uri
|
33
|
+
raise Sidetree::Error,
|
34
|
+
"Provisional proof file '#{proof_file_uri}' not allowed in a provisional index file with no updates"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parse provisional index file.
|
40
|
+
# @param [String] index_data provisional index file data.
|
41
|
+
# @param [Boolean] compressed Whether the +index_data+ is compressed or not, default: true.
|
42
|
+
# @return [Sidetree::Model::ProvisionalIndexFile]
|
43
|
+
def self.parse(index_data, compressed: true)
|
44
|
+
decompressed =
|
45
|
+
(
|
46
|
+
if compressed
|
47
|
+
begin
|
48
|
+
Sidetree::Util::Compressor.decompress(
|
49
|
+
index_data,
|
50
|
+
max_bytes: Sidetree::Params::MAX_PROVISIONAL_INDEX_FILE_SIZE
|
51
|
+
)
|
52
|
+
rescue Zlib::GzipFile::Error
|
53
|
+
raise Sidetree::Error,
|
54
|
+
"Provisional Index File decompression failure"
|
55
|
+
end
|
56
|
+
else
|
57
|
+
index_data
|
58
|
+
end
|
59
|
+
)
|
60
|
+
begin
|
61
|
+
json = JSON.parse(decompressed, symbolize_names: true)
|
62
|
+
operations = []
|
63
|
+
chunks = []
|
64
|
+
json.each do |k, v|
|
65
|
+
case k
|
66
|
+
when :chunks
|
67
|
+
chunks =
|
68
|
+
v
|
69
|
+
.map do |chunk|
|
70
|
+
chunk.map do |c_k, c_v|
|
71
|
+
case c_k
|
72
|
+
when :chunkFileUri
|
73
|
+
Sidetree::Model::Chunk.new(c_v)
|
74
|
+
else
|
75
|
+
raise Sidetree::Error,
|
76
|
+
"Provisional Index File chunk has missing or unknown property"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
.flatten
|
81
|
+
when :provisionalProofFileUri
|
82
|
+
when :operations
|
83
|
+
v.each do |o_k, o_v|
|
84
|
+
case o_k
|
85
|
+
when :update
|
86
|
+
unless o_v.is_a?(Array)
|
87
|
+
raise Sidetree::Error,
|
88
|
+
"Provisional Index File update operation not array"
|
89
|
+
end
|
90
|
+
operations =
|
91
|
+
o_v.map do |update|
|
92
|
+
Sidetree::OP::Update.from_json(update.to_json_c14n)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
raise Sidetree::Error,
|
96
|
+
"Unexpected property '#{o_k.to_s}' in update operation"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
else
|
100
|
+
raise Sidetree::Error,
|
101
|
+
"Unexpected property #{k.to_s} in provisional index file"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
ProvisionalIndexFile.new(
|
105
|
+
chunks: chunks,
|
106
|
+
operations: operations,
|
107
|
+
proof_file_uri: json[:provisionalProofFileUri]
|
108
|
+
)
|
109
|
+
rescue JSON::ParserError
|
110
|
+
raise Sidetree::Error, "Provisional index file is not json"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Compress this provisional index file
|
115
|
+
# @return [String] compressed data.
|
116
|
+
def to_compress
|
117
|
+
params = { chunks: chunks.map(&:to_h) }
|
118
|
+
unless operations.empty?
|
119
|
+
params[:operations] = {
|
120
|
+
update:
|
121
|
+
operations.map do |update|
|
122
|
+
{
|
123
|
+
didSuffix: update.did_suffix,
|
124
|
+
revealValue: update.revealed_value
|
125
|
+
}
|
126
|
+
end
|
127
|
+
}
|
128
|
+
params[:provisionalProofFileUri] = provisional_proof_file_uri
|
129
|
+
end
|
130
|
+
Sidetree::Util::Compressor.compress(params.to_json)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -10,10 +10,10 @@ module Sidetree
|
|
10
10
|
@recovery_commitment = recovery_commitment
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
13
|
+
# Create Suffix object from hash object.
|
14
14
|
# @return [Sidetree::Model::Suffix]
|
15
15
|
# @raise [Sidetree::Error]
|
16
|
-
def self.
|
16
|
+
def self.from_object(object)
|
17
17
|
Sidetree::Validator.validate_suffix_data!(object)
|
18
18
|
Suffix.new(object[:deltaHash], object[:recoveryCommitment])
|
19
19
|
end
|
data/lib/sidetree/model.rb
CHANGED
@@ -4,5 +4,9 @@ module Sidetree
|
|
4
4
|
autoload :Delta, "sidetree/model/delta"
|
5
5
|
autoload :Document, "sidetree/model/document"
|
6
6
|
autoload :Service, "sidetree/model/service"
|
7
|
+
autoload :CoreIndexFile, "sidetree/model/core_index_file"
|
8
|
+
autoload :ChunkFile, "sidetree/model/chunk_file"
|
9
|
+
autoload :Chunk, "sidetree/model/chunk"
|
10
|
+
autoload :ProvisionalIndexFile, "sidetree/model/provisional_index_file"
|
7
11
|
end
|
8
12
|
end
|
data/lib/sidetree/op/create.rb
CHANGED
@@ -29,6 +29,39 @@ module Sidetree
|
|
29
29
|
did.create_op
|
30
30
|
end
|
31
31
|
|
32
|
+
# Parse create operation data from json string
|
33
|
+
# @param [String] create_data create operation data(json string).
|
34
|
+
# @return [Sidetree::OP::Create]
|
35
|
+
# @raise [Sidetree::Error]
|
36
|
+
def self.from_json(create_data)
|
37
|
+
begin
|
38
|
+
json = JSON.parse(create_data, symbolize_names: true)
|
39
|
+
json.each do |k, v|
|
40
|
+
case k
|
41
|
+
when :type, :suffixData, :delta
|
42
|
+
else
|
43
|
+
raise Sidetree::Error,
|
44
|
+
"Create operation missing or unknown property"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
if json[:type] && json[:type] != Sidetree::OP::Type::CREATE
|
48
|
+
raise Sidetree::Error, "Create operation type incorrect"
|
49
|
+
end
|
50
|
+
suffix = Sidetree::Model::Suffix.from_object(json[:suffixData])
|
51
|
+
delta = nil
|
52
|
+
begin
|
53
|
+
# For compatibility with data pruning
|
54
|
+
delta = Sidetree::Model::Delta.from_object(json[:delta]) if json[
|
55
|
+
:delta
|
56
|
+
]
|
57
|
+
rescue Sidetree::Error
|
58
|
+
end
|
59
|
+
Create.new(suffix, delta)
|
60
|
+
rescue JSON::ParserError
|
61
|
+
raise Sidetree::Error, "create_data not json"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
32
65
|
def type
|
33
66
|
Sidetree::OP::Type::CREATE
|
34
67
|
end
|
@@ -53,8 +86,8 @@ module Sidetree
|
|
53
86
|
end
|
54
87
|
|
55
88
|
Create.new(
|
56
|
-
Sidetree::Model::Suffix.
|
57
|
-
Sidetree::Model::Delta.
|
89
|
+
Sidetree::Model::Suffix.from_object(json[:suffixData]),
|
90
|
+
Sidetree::Model::Delta.from_object(json[:delta])
|
58
91
|
)
|
59
92
|
rescue JSON::ParserError
|
60
93
|
raise Error, "Long form initial state should be encoded jcs."
|
@@ -62,7 +95,7 @@ module Sidetree
|
|
62
95
|
end
|
63
96
|
|
64
97
|
def to_h
|
65
|
-
{ suffixData: suffix.to_h, delta: delta
|
98
|
+
{ suffixData: suffix.to_h, delta: delta&.to_h }
|
66
99
|
end
|
67
100
|
|
68
101
|
# Generate long_suffix for DID.
|
@@ -1,7 +1,76 @@
|
|
1
1
|
module Sidetree
|
2
2
|
module OP
|
3
|
-
# Deactivate operation class
|
3
|
+
# Deactivate operation class
|
4
|
+
# https://identity.foundation/sidetree/spec/#deactivate
|
4
5
|
class Deactivate < Base
|
6
|
+
attr_reader :did_suffix
|
7
|
+
attr_reader :signed_data
|
8
|
+
attr_reader :revealed_value
|
9
|
+
|
10
|
+
# Initialize
|
11
|
+
# @param [String] did_suffix
|
12
|
+
# @param [JSON::JWS] signed_data
|
13
|
+
# @param [String] revealed_value
|
14
|
+
def initialize(did_suffix, signed_data, revealed_value)
|
15
|
+
Sidetree::Validator.validate_encoded_multi_hash!(
|
16
|
+
did_suffix,
|
17
|
+
"#{type} didSuffix"
|
18
|
+
)
|
19
|
+
Sidetree::Validator.validate_encoded_multi_hash!(
|
20
|
+
revealed_value,
|
21
|
+
"#{type} revealValue"
|
22
|
+
)
|
23
|
+
@did_suffix = did_suffix
|
24
|
+
@signed_data = signed_data
|
25
|
+
@revealed_value = revealed_value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Parse Deactivate operation data from json string
|
29
|
+
# @param [String] deactivate_data deactivate operation data(json string).
|
30
|
+
# @return [Sidetree::OP::Deactivate]
|
31
|
+
# @raise [Sidetree::Error]
|
32
|
+
def self.from_json(deactivate_data)
|
33
|
+
begin
|
34
|
+
json = JSON.parse(deactivate_data, symbolize_names: true)
|
35
|
+
jws, revealed_value, did_suffix = nil, nil, nil
|
36
|
+
json.each do |k, v|
|
37
|
+
case k
|
38
|
+
when :type
|
39
|
+
unless v == Sidetree::OP::Type::DEACTIVATE
|
40
|
+
raise Sidetree::Error, "Deactivate operation type incorrect"
|
41
|
+
end
|
42
|
+
when :didSuffix
|
43
|
+
did_suffix = v
|
44
|
+
when :revealValue
|
45
|
+
revealed_value = v
|
46
|
+
when :signedData
|
47
|
+
jws = JSON::JWS.decode_compact_serialized(v, :skip_verification)
|
48
|
+
unless jws.keys.length == 2
|
49
|
+
raise Sidetree::Error,
|
50
|
+
"Deactivate operation signed data missing or unknown property"
|
51
|
+
end
|
52
|
+
else
|
53
|
+
raise Sidetree::Error,
|
54
|
+
"Unexpected property #{k.to_s} in deactivate operation"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
if jws
|
58
|
+
Validator.validate_canonicalize_object_hash!(
|
59
|
+
jws["recoveryKey"],
|
60
|
+
revealed_value,
|
61
|
+
"Deactivate key"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
unless did_suffix
|
65
|
+
raise Sidetree::Error, "The deactivate didSuffix must be a string"
|
66
|
+
end
|
67
|
+
Deactivate.new(did_suffix, jws, revealed_value)
|
68
|
+
rescue JSON::ParserError
|
69
|
+
raise Sidetree::Error, "deactivate_data not json"
|
70
|
+
rescue JSON::JWS::InvalidFormat
|
71
|
+
raise Sidetree::Error, "Invalid signedData"
|
72
|
+
end
|
73
|
+
end
|
5
74
|
def type
|
6
75
|
Sidetree::OP::Type::DEACTIVATE
|
7
76
|
end
|
data/lib/sidetree/op/recover.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
module Sidetree
|
2
2
|
module OP
|
3
|
-
# Recover operation class.
|
4
|
-
|
3
|
+
# Recover operation class.
|
4
|
+
# https://identity.foundation/sidetree/spec/#recover
|
5
|
+
class Recover < Updatable
|
6
|
+
# Parse Recover operation data from json string
|
7
|
+
# @param [String] recover_data recover operation data(json string).
|
8
|
+
# @return [Sidetree::OP::Recover]
|
9
|
+
# @raise [Sidetree::Error]
|
10
|
+
def self.from_json(recover_data)
|
11
|
+
parse_json(recover_data, Sidetree::OP::Type::RECOVER)
|
12
|
+
end
|
13
|
+
|
5
14
|
def type
|
6
15
|
Sidetree::OP::Type::RECOVER
|
7
16
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module OP
|
3
|
+
class Updatable < Base
|
4
|
+
attr_reader :did_suffix
|
5
|
+
attr_reader :delta
|
6
|
+
attr_reader :signed_data
|
7
|
+
attr_reader :revealed_value
|
8
|
+
|
9
|
+
# Initialize
|
10
|
+
# @param [String] did_suffix
|
11
|
+
# @param [Sidetree::Model::Delta] delta
|
12
|
+
# @param [JSON::JWS] signed_data
|
13
|
+
# @param [String] revealed_value
|
14
|
+
def initialize(did_suffix, delta, signed_data, revealed_value)
|
15
|
+
Sidetree::Validator.validate_encoded_multi_hash!(
|
16
|
+
did_suffix,
|
17
|
+
"#{type} didSuffix"
|
18
|
+
)
|
19
|
+
Sidetree::Validator.validate_encoded_multi_hash!(
|
20
|
+
revealed_value,
|
21
|
+
"#{type} revealValue"
|
22
|
+
)
|
23
|
+
@did_suffix = did_suffix
|
24
|
+
@delta = delta
|
25
|
+
@signed_data = signed_data
|
26
|
+
@revealed_value = revealed_value
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_json(data, type)
|
30
|
+
begin
|
31
|
+
json = JSON.parse(data, symbolize_names: true)
|
32
|
+
delta, jws, revealed_value, did_suffix = nil, nil, nil, nil
|
33
|
+
json.each do |k, v|
|
34
|
+
case k
|
35
|
+
when :type
|
36
|
+
unless v == type
|
37
|
+
raise Sidetree::Error,
|
38
|
+
"#{type.capitalize} operation type incorrect"
|
39
|
+
end
|
40
|
+
when :didSuffix
|
41
|
+
did_suffix = v
|
42
|
+
when :revealValue
|
43
|
+
revealed_value = v
|
44
|
+
when :signedData
|
45
|
+
jws = JSON::JWS.decode_compact_serialized(v, :skip_verification)
|
46
|
+
unless jws.keys.length ==
|
47
|
+
(type == Sidetree::OP::Type::RECOVER ? 3 : 2)
|
48
|
+
raise Sidetree::Error,
|
49
|
+
"#{type.capitalize} operation signed data missing or unknown property"
|
50
|
+
end
|
51
|
+
when :delta
|
52
|
+
delta = Sidetree::Model::Delta.from_object(v)
|
53
|
+
else
|
54
|
+
raise Sidetree::Error,
|
55
|
+
"Unexpected property #{k.to_s} in #{type} operation"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if jws
|
59
|
+
key =
|
60
|
+
type == Sidetree::OP::Type::UPDATE ? "updateKey" : "recoveryKey"
|
61
|
+
Validator.validate_canonicalize_object_hash!(
|
62
|
+
jws[key],
|
63
|
+
revealed_value,
|
64
|
+
"#{type.capitalize} key"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
unless did_suffix
|
68
|
+
raise Sidetree::Error, "The #{type} didSuffix must be a string"
|
69
|
+
end
|
70
|
+
Module.const_get("Sidetree::OP::#{type.capitalize}").new(
|
71
|
+
did_suffix,
|
72
|
+
delta,
|
73
|
+
jws,
|
74
|
+
revealed_value
|
75
|
+
)
|
76
|
+
rescue JSON::ParserError
|
77
|
+
raise Sidetree::Error, "data dose not json"
|
78
|
+
rescue JSON::JWS::InvalidFormat
|
79
|
+
raise Sidetree::Error, "Invalid signedData"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module OP
|
3
|
+
# Update operation class.
|
4
|
+
# https://identity.foundation/sidetree/spec/#update
|
5
|
+
class Update < Updatable
|
6
|
+
# Parse update operation data from json string
|
7
|
+
# @param [String] update_data update operation data(json string).
|
8
|
+
# @return [Sidetree::OP::Update]
|
9
|
+
# @raise [Sidetree::Error]
|
10
|
+
def self.from_json(update_data)
|
11
|
+
parse_json(update_data, Sidetree::OP::Type::UPDATE)
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
Sidetree::OP::Type::UPDATE
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/sidetree/op.rb
CHANGED
@@ -32,8 +32,10 @@ module Sidetree
|
|
32
32
|
end
|
33
33
|
|
34
34
|
autoload :Base, "sidetree/op/base"
|
35
|
+
autoload :Updatable, "sidetree/op/updatable"
|
35
36
|
autoload :Create, "sidetree/op/create"
|
36
37
|
autoload :Recover, "sidetree/op/recover"
|
38
|
+
autoload :Update, "sidetree/op/update"
|
37
39
|
autoload :Deactivate, "sidetree/op/deactivate"
|
38
40
|
end
|
39
41
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Sidetree
|
2
|
+
module Util
|
3
|
+
module AnchoredDataSerializer
|
4
|
+
DELIMITER = "."
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Serialize given data as Anchor String.
|
9
|
+
# @param [Integer] op_count Number of operations
|
10
|
+
# @param [String] uri Core Index File uri.
|
11
|
+
# @return [String] Anchor String
|
12
|
+
# @raise [Sidetree::Error]
|
13
|
+
def serialize(op_count, uri)
|
14
|
+
if op_count > Sidetree::Params::MAX_OPERATION_COUNT
|
15
|
+
raise Sidetree::Error, "Number of operations greater than max"
|
16
|
+
end
|
17
|
+
"#{op_count}#{DELIMITER}#{uri}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Deserializes the given string that is read from the blockchain into data.
|
21
|
+
# @param [String] anchor_str Anchor String
|
22
|
+
# @return [Array[Integer, String]]
|
23
|
+
# @raise [Sidetree::Error]
|
24
|
+
def deserialize(anchor_str)
|
25
|
+
data = anchor_str.split(DELIMITER)
|
26
|
+
raise Sidetree::Error, "Invalid anchor string" unless data.length == 2
|
27
|
+
unless data[0] =~ /^[1-9]\d*$/
|
28
|
+
raise Sidetree::Error,
|
29
|
+
"Number of operations in anchor string is not positive number"
|
30
|
+
end
|
31
|
+
|
32
|
+
count = data[0].to_i
|
33
|
+
if count > Sidetree::Params::MAX_OPERATION_COUNT
|
34
|
+
raise Sidetree::Error,
|
35
|
+
"Number of operations in anchor string greater than max"
|
36
|
+
end
|
37
|
+
[count, data[1]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "zlib"
|
2
|
+
|
3
|
+
module Sidetree
|
4
|
+
module Util
|
5
|
+
module Compressor
|
6
|
+
# The estimated ratio/multiplier of decompressed Sidetree CAS file size compared against the compressed file size.
|
7
|
+
ESTIMATE_DECOMPRESSION_MULTIPLIER = 3
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Compresses teh data in gzip and return it as buffer.
|
12
|
+
# @param [String] data Data to be compressed.
|
13
|
+
# @return [String] compressed data.
|
14
|
+
def compress(data)
|
15
|
+
io = StringIO.new("w")
|
16
|
+
Zlib::GzipWriter.wrap(io) do |w|
|
17
|
+
w.mtime = 0
|
18
|
+
w.write data
|
19
|
+
end
|
20
|
+
io.string.force_encoding("binary")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Decompresses +compressed+.
|
24
|
+
# @param [String] compressed compressed data.
|
25
|
+
# @return [String] decompressed data.
|
26
|
+
# @raise [Sidetree::Error] raise if data exceeds max_bytes size.
|
27
|
+
def decompress(compressed, max_bytes: nil)
|
28
|
+
if max_bytes && compressed.bytesize > max_bytes
|
29
|
+
raise Sidetree::Error, "Exceed maximum compressed chunk file size."
|
30
|
+
end
|
31
|
+
io = StringIO.new(compressed)
|
32
|
+
result = StringIO.new
|
33
|
+
Zlib::GzipReader.wrap(io) { |gz| result << gz.read }
|
34
|
+
result.string
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/sidetree/validator.rb
CHANGED
@@ -215,6 +215,18 @@ module Sidetree
|
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
218
|
+
# Verify that the Multihash of +content+ matches that of +multihash+.
|
219
|
+
# @param [String] content content to be hashed
|
220
|
+
# @param [String] multihash Hash value to be checked
|
221
|
+
# @param [String] target Name of the target object to include in the error message
|
222
|
+
# @raise [Sidetree::Error]
|
223
|
+
def validate_canonicalize_object_hash!(content, multihash, target)
|
224
|
+
unless Sidetree.to_hash(content) == multihash
|
225
|
+
raise Sidetree::Error,
|
226
|
+
"Canonicalized #{target} object hash does not match expected hash '#{multihash}'"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
218
230
|
def validate_did_type!(type)
|
219
231
|
return unless type
|
220
232
|
raise Error, "DID type must be a string." unless type.instance_of?(String)
|
@@ -245,5 +257,16 @@ module Sidetree
|
|
245
257
|
validate_encoded_multi_hash!(suffix[:deltaHash], "delta hash")
|
246
258
|
validate_did_type!(suffix[:type])
|
247
259
|
end
|
260
|
+
|
261
|
+
def validate_cas_file_uri!(uri, target)
|
262
|
+
unless uri.is_a?(String)
|
263
|
+
raise Sidetree::Error,
|
264
|
+
"Input #{target} CAS file URI '#{uri}' needs to be of string type"
|
265
|
+
end
|
266
|
+
if uri.bytesize > Sidetree::Params::MAX_CAS_URI_LENGTH
|
267
|
+
raise Sidetree::Error,
|
268
|
+
"Input #{target} CAS file URI byte size cannot exceed #{Sidetree::Params::MAX_CAS_URI_LENGTH}"
|
269
|
+
end
|
270
|
+
end
|
248
271
|
end
|
249
272
|
end
|
data/lib/sidetree/version.rb
CHANGED
data/lib/sidetree.rb
CHANGED
@@ -13,11 +13,13 @@ module Sidetree
|
|
13
13
|
class Error < StandardError
|
14
14
|
end
|
15
15
|
|
16
|
+
autoload :Util, "sidetree/util"
|
16
17
|
autoload :Key, "sidetree/key"
|
17
18
|
autoload :DID, "sidetree/did"
|
18
19
|
autoload :Model, "sidetree/model"
|
19
20
|
autoload :OP, "sidetree/op"
|
20
21
|
autoload :Validator, "sidetree/validator"
|
22
|
+
autoload :CAS, "sidetree/cas"
|
21
23
|
|
22
24
|
module Params
|
23
25
|
# Algorithm for generating hashes of protocol-related values. 0x12 = sha2-256
|
@@ -26,7 +28,20 @@ module Sidetree
|
|
26
28
|
|
27
29
|
# Maximum canonicalized operation delta buffer size.
|
28
30
|
MAX_DELTA_SIZE = 1000
|
29
|
-
|
31
|
+
# Maximum compressed chunk file size. 10MB.
|
32
|
+
MAX_CHUNK_FILE_SIZE = 10_000_000
|
33
|
+
# Maximum compressed Provisional Index File size. 1 MB (zipped)
|
34
|
+
MAX_PROVISIONAL_INDEX_FILE_SIZE = 1_000_000
|
35
|
+
# Maximum compressed Core Index File size. 1 MB (zipped)
|
36
|
+
MAX_CORE_INDEX_FILE_SIZE = 1_000_000
|
37
|
+
# Maximum writer lock ID size
|
38
|
+
MAX_WRITER_LOCK_ID_SIZE = 200
|
39
|
+
# Maximum length of CAS URIs. 100 bytes
|
40
|
+
MAX_CAS_URI_LENGTH = 100
|
41
|
+
# Maximum number of operations per batch. 10000 ops.
|
42
|
+
MAX_OPERATION_COUNT = 10_000
|
43
|
+
# Default public key type
|
44
|
+
DEFAULT_PUBKEY_TYPE = "EcdsaSecp256k1VerificationKey2019"
|
30
45
|
# Default DID method
|
31
46
|
DEFAULT_METHOD = "sidetree"
|
32
47
|
|
data/sidetree.gemspec
CHANGED
@@ -39,5 +39,6 @@ Gem::Specification.new do |spec|
|
|
39
39
|
# For more information and examples about making a new gem, checkout our
|
40
40
|
# guide at: https://bundler.io/guides/creating_gem.html
|
41
41
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
42
|
-
spec.add_development_dependency 'prettier', '~> 3.
|
42
|
+
spec.add_development_dependency 'prettier', '~> 3.2.0'
|
43
|
+
spec.add_development_dependency 'webmock', '~> 3.14.0'
|
43
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidetree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|
@@ -86,14 +86,28 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 3.
|
89
|
+
version: 3.2.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 3.
|
96
|
+
version: 3.2.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.14.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.14.0
|
97
111
|
description: Ruby implementation for Sidetree protocol.
|
98
112
|
email:
|
99
113
|
- azuchi@chaintope.com
|
@@ -116,11 +130,18 @@ files:
|
|
116
130
|
- bin/console
|
117
131
|
- bin/setup
|
118
132
|
- lib/sidetree.rb
|
133
|
+
- lib/sidetree/cas.rb
|
134
|
+
- lib/sidetree/cas/fetch_result.rb
|
135
|
+
- lib/sidetree/cas/ipfs.rb
|
119
136
|
- lib/sidetree/did.rb
|
120
137
|
- lib/sidetree/key.rb
|
121
138
|
- lib/sidetree/model.rb
|
139
|
+
- lib/sidetree/model/chunk.rb
|
140
|
+
- lib/sidetree/model/chunk_file.rb
|
141
|
+
- lib/sidetree/model/core_index_file.rb
|
122
142
|
- lib/sidetree/model/delta.rb
|
123
143
|
- lib/sidetree/model/document.rb
|
144
|
+
- lib/sidetree/model/provisional_index_file.rb
|
124
145
|
- lib/sidetree/model/service.rb
|
125
146
|
- lib/sidetree/model/suffix.rb
|
126
147
|
- lib/sidetree/op.rb
|
@@ -128,6 +149,11 @@ files:
|
|
128
149
|
- lib/sidetree/op/create.rb
|
129
150
|
- lib/sidetree/op/deactivate.rb
|
130
151
|
- lib/sidetree/op/recover.rb
|
152
|
+
- lib/sidetree/op/updatable.rb
|
153
|
+
- lib/sidetree/op/update.rb
|
154
|
+
- lib/sidetree/util.rb
|
155
|
+
- lib/sidetree/util/anchored_data_serializer.rb
|
156
|
+
- lib/sidetree/util/compressor.rb
|
131
157
|
- lib/sidetree/validator.rb
|
132
158
|
- lib/sidetree/version.rb
|
133
159
|
- sidetree.gemspec
|