Skip to content

Commit 171023d

Browse files
committed
Support MerkleTree v3.0: Add V3 DTOs, serialization, parsing, and tests
- Add support for MerkleTree version 3.0 with JWT-style header and protected header leaf. - Implement `MerkleTreeV3Dto`, `MerkleTreeV3LeafDto`, `MerkleTreeV3HeaderDto`, and `MerkleTreeV3LeafHeaderDto` for v3.0 serialization/deserialization. - Update `MerkleTree` to handle v3.0 in `ToJson`, `Parse`, and root computation, including header leaf logic and exchange document type. - Add `ExchangeDocumentType` to `MerkleMetadata` and update version constants. - Add `InvalidLeafHashException` for leaf hash validation errors. - Refactor `MerkleLeaf` to use a shared hash function utility. - Update v2.0 DTOs to use `JsonPropertyName` attributes for consistency. - Add comprehensive tests for v3.0 in `MerkleTreeV3Tests.cs`, including roundtrip, tampering, header protection, and privacy scenarios.
1 parent 13bdf5d commit 171023d

File tree

9 files changed

+1619
-103
lines changed

9 files changed

+1619
-103
lines changed

.vscode/launch.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
// Use IntelliSense to find out which attributes exist for C# debugging
6+
// Use hover for the description of the existing attributes
7+
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
8+
"name": ".NET Core Launch (console)",
9+
"type": "coreclr",
10+
"request": "launch",
11+
"preLaunchTask": "build",
12+
// If you have changed target frameworks, make sure to update the program path.
13+
"program": "${workspaceFolder}/tests/Evoq.Blockchain.Tests/bin/Debug/net7.0/Evoq.Blockchain.Tests.dll",
14+
"args": [],
15+
"cwd": "${workspaceFolder}/tests/Evoq.Blockchain.Tests",
16+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17+
"console": "internalConsole",
18+
"stopAtEntry": false
19+
},
20+
{
21+
"name": ".NET Core Attach",
22+
"type": "coreclr",
23+
"request": "attach"
24+
}
25+
]
26+
}

.vscode/tasks.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/Evoq.Blockchain.sln",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary;ForceNoAlign"
13+
],
14+
"problemMatcher": "$msCompile"
15+
},
16+
{
17+
"label": "publish",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"publish",
22+
"${workspaceFolder}/Evoq.Blockchain.sln",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary;ForceNoAlign"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
28+
{
29+
"label": "watch",
30+
"command": "dotnet",
31+
"type": "process",
32+
"args": [
33+
"watch",
34+
"run",
35+
"--project",
36+
"${workspaceFolder}/Evoq.Blockchain.sln"
37+
],
38+
"problemMatcher": "$msCompile"
39+
}
40+
]
41+
}

src/Evoq.Blockchain/Blockchain.Merkle/InvalidRootException.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,44 @@ public InvalidRootException(string message, Exception innerException) : base(mes
4141
protected InvalidRootException(SerializationInfo info, StreamingContext context) : base(info, context)
4242
{
4343
}
44+
}
45+
46+
/// <summary>
47+
/// Exception thrown when the hash of a leaf does not match the computed hash.
48+
/// </summary>
49+
[Serializable]
50+
public class InvalidLeafHashException : Exception
51+
{
52+
/// <summary>
53+
/// Initializes a new instance of the InvalidLeafHashException class.
54+
/// </summary>
55+
public InvalidLeafHashException()
56+
{
57+
}
58+
59+
/// <summary>
60+
/// Initializes a new instance of the InvalidLeafHashException class with a specified error message.
61+
/// </summary>
62+
/// <param name="message">The error message that explains the reason for the exception.</param>
63+
public InvalidLeafHashException(string message) : base(message)
64+
{
65+
}
66+
67+
/// <summary>
68+
/// Initializes a new instance of the InvalidLeafHashException class with a specified error message and a reference to the inner exception that is the cause of this exception.
69+
/// </summary>
70+
/// <param name="message">The error message that explains the reason for the exception.</param>
71+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
72+
public InvalidLeafHashException(string message, Exception innerException) : base(message, innerException)
73+
{
74+
}
75+
76+
/// <summary>
77+
/// Initializes a new instance of the InvalidLeafHashException class with serialized data.
78+
/// </summary>
79+
/// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
80+
/// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
81+
protected InvalidLeafHashException(SerializationInfo info, StreamingContext context) : base(info, context)
82+
{
83+
}
4484
}

src/Evoq.Blockchain/Blockchain.Merkle/MerkleLeaf.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static MerkleLeaf FromData(string contentType, Hex data)
9595
/// <returns>A new MerkleLeaf with the specified content type.</returns>
9696
public static MerkleLeaf FromData(string contentType, Hex data, Hex salt, MerkleTree.HashFunction hashFunction)
9797
{
98-
var hash = hashFunction(Hex.Concat(data, salt).ToByteArray());
98+
var hash = UseHashFunction(hashFunction, data, salt);
9999

100100
return new MerkleLeaf(contentType, data, salt, hash);
101101
}
@@ -128,7 +128,7 @@ public static MerkleLeaf FromJsonValue(string fieldName, object? fieldValue, Hex
128128

129129
var json = JsonSerializer.Serialize(jsonObject);
130130
var data = new Hex(System.Text.Encoding.UTF8.GetBytes(json));
131-
var hash = hashFunction(Hex.Concat(data, salt).ToByteArray());
131+
var hash = UseHashFunction(hashFunction, data, salt);
132132

133133
return new MerkleLeaf(ContentTypeUtility.CreateJsonUtf8(), data, salt, hash);
134134
}
@@ -178,4 +178,11 @@ public override string ToString()
178178

179179
return this.Data.ToString();
180180
}
181+
182+
//
183+
184+
internal static Hex UseHashFunction(MerkleTree.HashFunction hashFunction, Hex data, Hex salt)
185+
{
186+
return hashFunction(Hex.Concat(data, salt).ToByteArray());
187+
}
181188
}

src/Evoq.Blockchain/Blockchain.Merkle/MerkleMetadata.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public static class MerkleTreeVersionStrings
1414
/// The version string for the Merkle tree version 2.0.
1515
/// </summary>
1616
public const string V2_0 = "MerkleTree+2.0";
17+
18+
/// <summary>
19+
/// The version string for the Merkle tree version 3.0.
20+
/// </summary>
21+
public const string V3_0 = "application/merkle-exchange-3.0+json";
1722
}
1823

1924
/// <summary>
@@ -46,4 +51,9 @@ public class MerkleMetadata
4651
/// Gets or sets the version of the Merkle tree implementation.
4752
/// </summary>
4853
public string Version { get; set; } = MerkleTreeVersionStrings.V1_0;
54+
55+
/// <summary>
56+
/// Gets or sets the type of the document or record being exchanged in the leaf data.
57+
/// </summary>
58+
public string? ExchangeDocumentType { get; set; }
4959
}

0 commit comments

Comments
 (0)