Skip to content

Commit d30f394

Browse files
authored
Add merkle log proof mechanism (#257)
The ability to specify merkle log as the proof mechanism is added. Note that this requires backend support which will only be available at some future date. AB#9103
1 parent 7e637e4 commit d30f394

6 files changed

Lines changed: 212 additions & 47 deletions

File tree

‎archivist/proof_mechanism.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ class ProofMechanism(Enum):
1717
__RESERVED = 1
1818
#: Assets and events are proven using a hash of the originator's evidence
1919
SIMPLE_HASH = 2
20+
#: Assets and events are proven using a merkle log hash of the originator's evidence
21+
MERKLE_LOG = 3

‎docs/spelling_wordlist.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jupyter
2929
jupyterLabDesktop
3030
kwargs
3131
macclesfield
32+
merkle
3233
mimetype
3334
onwards
3435
params

‎functests/execassets.py‎

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
}
3535

3636
ASSET_NAME = "Telephone with 2 attachments - one bad or not scanned 2022-03-01"
37-
REQUEST_EXISTS_ATTACHMENTS = {
37+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH = {
3838
"selector": [
3939
{
4040
"attributes": [
@@ -67,6 +67,39 @@
6767
},
6868
],
6969
}
70+
REQUEST_EXISTS_ATTACHMENTS_MERKLE_LOG = {
71+
"selector": [
72+
{
73+
"attributes": [
74+
"arc_display_name",
75+
"arc_namespace",
76+
]
77+
},
78+
],
79+
"behaviours": ASSET_BEHAVIOURS,
80+
"proof_mechanism": ProofMechanism.MERKLE_LOG.name,
81+
"attributes": {
82+
"arc_display_name": ASSET_NAME,
83+
"arc_namespace": getenv("DATATRAILS_UNIQUE_ID"),
84+
"arc_firmware_version": "1.0",
85+
"arc_serial_number": "vtl-x4-07",
86+
"arc_description": "Traffic flow control light at A603 North East",
87+
"arc_display_type": "Traffic light with violation camera",
88+
"some_custom_attribute": "value",
89+
},
90+
"attachments": [
91+
{
92+
"filename": "functests/test_resources/telephone.jpg",
93+
"content_type": "image/jpg",
94+
"attachment": "telephone",
95+
},
96+
{
97+
"url": "https://secure.eicar.org/eicarcom2.zip",
98+
"content_type": "application/zip",
99+
"attachment": "zipfile",
100+
},
101+
],
102+
}
70103

71104

72105
class TestAssetCreate(TestCase):
@@ -163,12 +196,54 @@ def test_asset_create_with_fixtures(self):
163196
msg="Incorrect number of fancy_traffic_lights",
164197
)
165198

166-
def test_asset_create_event(self):
199+
def test_asset_create_event_merkle_log(self):
200+
"""
201+
Test list
202+
"""
203+
asset, existed = self.arch.assets.create_if_not_exists(
204+
REQUEST_EXISTS_ATTACHMENTS_MERKLE_LOG,
205+
confirm=True,
206+
)
207+
identity = asset["identity"]
208+
209+
# different behaviours are also different.
210+
props = {
211+
"operation": "Record",
212+
# This event is used to record evidence.
213+
"behaviour": "RecordEvidence",
214+
# Optional Client-claimed time at which the maintenance was performed
215+
"timestamp_declared": "2019-11-27T14:44:19Z",
216+
# Optional Client-claimed identity of person performing the operation
217+
"principal_declared": {
218+
"issuer": "idp.synsation.io/1234",
219+
"subject": "phil.b",
220+
"email": "phil.b@synsation.io",
221+
},
222+
}
223+
attrs = {
224+
# Required Details of the RecordEvidence request
225+
"arc_description": "Safety conformance approved for version 1.6.",
226+
# Required The evidence to be retained in the asset history
227+
"arc_evidence": "DVA Conformance Report attached",
228+
# Example Client can add any additional information in further attributes,
229+
# including free text or attachments
230+
"conformance_report": "blobs/e2a1d16c-03cd-45a1-8cd0-690831df1273",
231+
}
232+
233+
event = self.arch.events.create(
234+
identity, props=props, attrs=attrs, confirm=True
235+
)
236+
LOGGER.debug("event %s", json_dumps(event, sort_keys=True, indent=4))
237+
238+
tenancy = self.arch.tenancies.publicinfo(event["tenant_identity"])
239+
LOGGER.debug("tenancy %s", json_dumps(tenancy, sort_keys=True, indent=4))
240+
241+
def test_asset_create_event_simple_hash(self):
167242
"""
168243
Test list
169244
"""
170245
asset, existed = self.arch.assets.create_if_not_exists(
171-
REQUEST_EXISTS_ATTACHMENTS,
246+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH,
172247
confirm=True,
173248
)
174249
identity = asset["identity"]
@@ -239,7 +314,7 @@ def test_asset_create_if_not_exists_with_bad_attachment(self):
239314
240315
"""
241316
asset, existed = self.arch.assets.create_if_not_exists(
242-
REQUEST_EXISTS_ATTACHMENTS,
317+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH,
243318
confirm=True,
244319
)
245320
LOGGER.debug("asset %s", json_dumps(asset, indent=4))
@@ -291,7 +366,7 @@ def test_asset_create_if_not_exists_with_bad_attachment_assetattachment(self):
291366
292367
"""
293368
asset, existed = self.arch.assets.create_if_not_exists(
294-
REQUEST_EXISTS_ATTACHMENTS,
369+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH,
295370
confirm=True,
296371
)
297372
LOGGER.debug("asset %s", json_dumps(asset, indent=4))

‎unittests/testassets.py‎

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@
2020
ASSET_NAME,
2121
ATTRS,
2222
FIXTURES,
23+
MERKLE_LOG,
2324
PRIMARY_IMAGE,
24-
PROPS,
25-
REQUEST_EXISTS,
26-
REQUEST_EXISTS_ATTACHMENTS,
27-
REQUEST_EXISTS_KWARGS,
28-
REQUEST_EXISTS_KWARGS_ATTACHMENTS,
29-
REQUEST_EXISTS_KWARGS_LOCATION,
30-
REQUEST_EXISTS_LOCATION,
31-
REQUEST_EXISTS_LOCATION_IDENTITY,
32-
REQUEST_FIXTURES_KWARGS,
33-
REQUEST_KWARGS,
25+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH,
26+
REQUEST_EXISTS_KWARGS_ATTACHMENTS_SIMPLE_HASH,
27+
REQUEST_EXISTS_KWARGS_LOCATION_SIMPLE_HASH,
28+
REQUEST_EXISTS_KWARGS_SIMPLE_HASH,
29+
REQUEST_EXISTS_LOCATION_IDENTITY_SIMPLE_HASH,
30+
REQUEST_EXISTS_LOCATION_SIMPLE_HASH,
31+
REQUEST_EXISTS_SIMPLE_HASH,
32+
REQUEST_FIXTURES_KWARGS_SIMPLE_HASH,
33+
REQUEST_KWARGS_MERKLE_LOG,
34+
REQUEST_KWARGS_SIMPLE_HASH,
3435
RESPONSE,
3536
RESPONSE_ATTACHMENTS,
3637
RESPONSE_EXISTS,
@@ -41,6 +42,7 @@
4142
RESPONSE_LOCATION,
4243
RESPONSE_NO_CONFIRMATION,
4344
RESPONSE_PENDING,
45+
SIMPLE_HASH,
4446
SUBPATH,
4547
TestAssetsBase,
4648
TestAssetsBaseConfirm,
@@ -84,7 +86,9 @@ def test_assets_create(self):
8486
with mock.patch.object(self.arch.session, "post", autospec=True) as mock_post:
8587
mock_post.return_value = MockResponse(200, **RESPONSE)
8688

87-
asset = self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=False)
89+
asset = self.arch.assets.create(
90+
props=SIMPLE_HASH, attrs=ATTRS, confirm=False
91+
)
8892
args, kwargs = mock_post.call_args
8993
self.assertEqual(
9094
args,
@@ -93,7 +97,44 @@ def test_assets_create(self):
9397
)
9498
self.assertEqual(
9599
kwargs,
96-
REQUEST_KWARGS,
100+
REQUEST_KWARGS_SIMPLE_HASH,
101+
msg="CREATE method kwargs called incorrectly",
102+
)
103+
self.assertEqual(
104+
asset,
105+
RESPONSE,
106+
msg="CREATE incorrect response",
107+
)
108+
self.assertEqual(
109+
asset.primary_image,
110+
PRIMARY_IMAGE,
111+
msg="Incorrect primary image",
112+
)
113+
self.assertEqual(
114+
asset.name,
115+
ASSET_NAME,
116+
msg="Incorrect name property",
117+
)
118+
119+
def test_assets_create_merkle_log(self):
120+
"""
121+
Test asset creation
122+
"""
123+
with mock.patch.object(self.arch.session, "post", autospec=True) as mock_post:
124+
mock_post.return_value = MockResponse(200, **RESPONSE)
125+
126+
asset = self.arch.assets.create(
127+
props=MERKLE_LOG, attrs=ATTRS, confirm=False
128+
)
129+
args, kwargs = mock_post.call_args
130+
self.assertEqual(
131+
args,
132+
(f"url/{ROOT}/{ASSETS_SUBPATH}/{ASSETS_LABEL}",),
133+
msg="CREATE method args called incorrectly",
134+
)
135+
self.assertEqual(
136+
kwargs,
137+
REQUEST_KWARGS_MERKLE_LOG,
97138
msg="CREATE method kwargs called incorrectly",
98139
)
99140
self.assertEqual(
@@ -120,7 +161,7 @@ def test_assets_create_with_fixtures(self):
120161
arch.fixtures = FIXTURES
121162
with mock.patch.object(arch.session, "post", autospec=True) as mock_post:
122163
mock_post.return_value = MockResponse(200, **RESPONSE_FIXTURES)
123-
asset = arch.assets.create(props=PROPS, attrs=ATTRS, confirm=False)
164+
asset = arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=False)
124165
args, kwargs = mock_post.call_args
125166
self.assertEqual(
126167
args,
@@ -129,7 +170,7 @@ def test_assets_create_with_fixtures(self):
129170
)
130171
self.assertEqual(
131172
kwargs,
132-
REQUEST_FIXTURES_KWARGS,
173+
REQUEST_FIXTURES_KWARGS_SIMPLE_HASH,
133174
msg="CREATE method kwargs called incorrectly",
134175
)
135176
self.assertEqual(
@@ -149,7 +190,7 @@ def test_assets_create_with_confirmation_no_confirmed_status(self):
149190
mock_get.return_value = MockResponse(200, **RESPONSE_NO_CONFIRMATION)
150191

151192
with self.assertRaises(ArchivistUnconfirmedError):
152-
self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=True)
193+
self.arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=True)
153194

154195
def test_assets_create_with_confirmation_failed_status(self):
155196
"""
@@ -164,7 +205,7 @@ def test_assets_create_with_confirmation_failed_status(self):
164205
MockResponse(200, **RESPONSE_FAILED),
165206
]
166207
with self.assertRaises(ArchivistUnconfirmedError):
167-
self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=True)
208+
self.arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=True)
168209

169210
def test_assets_create_with_confirmation_always_pending_status(self):
170211
"""
@@ -184,7 +225,7 @@ def test_assets_create_with_confirmation_always_pending_status(self):
184225
MockResponse(200, **RESPONSE_PENDING),
185226
]
186227
with self.assertRaises(ArchivistUnconfirmedError):
187-
self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=True)
228+
self.arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=True)
188229

189230

190231
class TestAssetsCreateConfirm(TestAssetsBaseConfirm):
@@ -201,7 +242,9 @@ def test_assets_create_with_confirmation(self):
201242
) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get:
202243
mock_post.return_value = MockResponse(200, **RESPONSE)
203244
mock_get.return_value = MockResponse(200, **RESPONSE)
204-
asset = self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=True)
245+
asset = self.arch.assets.create(
246+
props=SIMPLE_HASH, attrs=ATTRS, confirm=True
247+
)
205248
self.assertEqual(
206249
asset,
207250
RESPONSE,
@@ -217,7 +260,9 @@ def test_assets_create_with_explicit_confirmation(self):
217260
) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get:
218261
mock_post.return_value = MockResponse(200, **RESPONSE)
219262
mock_get.return_value = MockResponse(200, **RESPONSE)
220-
asset = self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=False)
263+
asset = self.arch.assets.create(
264+
props=SIMPLE_HASH, attrs=ATTRS, confirm=False
265+
)
221266
self.arch.assets.wait_for_confirmation(asset["identity"])
222267
self.assertEqual(
223268
asset,
@@ -237,7 +282,9 @@ def test_assets_create_with_confirmation_pending_status(self):
237282
MockResponse(200, **RESPONSE_PENDING),
238283
MockResponse(200, **RESPONSE),
239284
]
240-
asset = self.arch.assets.create(props=PROPS, attrs=ATTRS, confirm=True)
285+
asset = self.arch.assets.create(
286+
props=SIMPLE_HASH, attrs=ATTRS, confirm=True
287+
)
241288
self.assertEqual(
242289
asset,
243290
RESPONSE,
@@ -266,7 +313,7 @@ def test_assets_create_if_not_exists_existing_asset(self):
266313
mock_post.return_value = MockResponse(200, **RESPONSE)
267314

268315
asset, existed = self.arch.assets.create_if_not_exists(
269-
data=REQUEST_EXISTS,
316+
data=REQUEST_EXISTS_SIMPLE_HASH,
270317
confirm=False,
271318
)
272319
mock_post.assert_not_called()
@@ -389,16 +436,18 @@ def test_assets_create_if_not_exists_nonexistent_asset(self):
389436
Test asset creation
390437
"""
391438
self.common_assets_create_if_not_exists_nonexistent_asset(
392-
REQUEST_EXISTS, REQUEST_EXISTS_KWARGS, RESPONSE_EXISTS
439+
REQUEST_EXISTS_SIMPLE_HASH,
440+
REQUEST_EXISTS_KWARGS_SIMPLE_HASH,
441+
RESPONSE_EXISTS,
393442
)
394443

395444
def test_assets_create_if_not_exists_nonexistent_asset_location(self):
396445
"""
397446
Test asset creation
398447
"""
399448
self.common_assets_create_if_not_exists_nonexistent_asset(
400-
REQUEST_EXISTS_LOCATION,
401-
REQUEST_EXISTS_KWARGS_LOCATION,
449+
REQUEST_EXISTS_LOCATION_SIMPLE_HASH,
450+
REQUEST_EXISTS_KWARGS_LOCATION_SIMPLE_HASH,
402451
RESPONSE_EXISTS_LOCATION,
403452
loc_resp=RESPONSE_LOCATION,
404453
)
@@ -408,8 +457,8 @@ def test_assets_create_if_not_exists_nonexistent_asset_location_identity(self):
408457
Test asset creation
409458
"""
410459
self.common_assets_create_if_not_exists_nonexistent_asset(
411-
REQUEST_EXISTS_LOCATION_IDENTITY,
412-
REQUEST_EXISTS_KWARGS_LOCATION,
460+
REQUEST_EXISTS_LOCATION_IDENTITY_SIMPLE_HASH,
461+
REQUEST_EXISTS_KWARGS_LOCATION_SIMPLE_HASH,
413462
RESPONSE_EXISTS_LOCATION,
414463
)
415464

@@ -418,8 +467,8 @@ def test_assets_create_if_not_exists_nonexistent_asset_attachments(self):
418467
Test asset creation
419468
"""
420469
self.common_assets_create_if_not_exists_nonexistent_asset(
421-
REQUEST_EXISTS_ATTACHMENTS,
422-
REQUEST_EXISTS_KWARGS_ATTACHMENTS,
470+
REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH,
471+
REQUEST_EXISTS_KWARGS_ATTACHMENTS_SIMPLE_HASH,
423472
RESPONSE_EXISTS_ATTACHMENTS,
424473
attachments_resp=RESPONSE_ATTACHMENTS,
425474
)

0 commit comments

Comments
 (0)