+70
PDSharp.Core/Cid.fs
+70
PDSharp.Core/Cid.fs
···
···
1
+
namespace PDSharp.Core
2
+
3
+
open System
4
+
open System.Text
5
+
6
+
/// Minimal Base32 (RFC 4648 Lowercase)
7
+
module Base32Encoding =
8
+
let private alphabet = "abcdefghijklmnopqrstuvwxyz234567"
9
+
10
+
let ToString (data : byte[]) : string =
11
+
if data.Length = 0 then
12
+
""
13
+
else
14
+
let mutable i = 0
15
+
let mutable index = 0
16
+
let mutable digit = 0
17
+
let mutable currByte = 0
18
+
let mutable nextByte = 0
19
+
let sb = StringBuilder((data.Length + 7) * 8 / 5)
20
+
21
+
while i < data.Length do
22
+
currByte <- (int data.[i]) &&& 0xFF
23
+
24
+
if index > 3 then
25
+
if (i + 1) < data.Length then
26
+
nextByte <- (int data.[i + 1]) &&& 0xFF
27
+
else
28
+
nextByte <- 0
29
+
30
+
digit <- currByte &&& (0xFF >>> index)
31
+
index <- (index + 5) % 8
32
+
digit <- digit <<< index
33
+
digit <- digit ||| (nextByte >>> (8 - index))
34
+
i <- i + 1
35
+
else
36
+
digit <- currByte >>> 8 - (index + 5) &&& 0x1F
37
+
index <- (index + 5) % 8
38
+
39
+
if index = 0 then
40
+
i <- i + 1
41
+
42
+
sb.Append(alphabet.[digit]) |> ignore
43
+
44
+
sb.ToString()
45
+
46
+
/// Basic CID implementation for AT Protocol (CIDv1 + dag-cbor + sha2-256)
47
+
///
48
+
/// Constants for ATProto defaults:
49
+
/// - Version 1 (0x01)
50
+
/// - Codec: dag-cbor (0x71)
51
+
/// - Hash: sha2-256 (0x12) - Length 32 (0x20)
52
+
[<Struct>]
53
+
type Cid =
54
+
val Bytes : byte[]
55
+
new(bytes : byte[]) = { Bytes = bytes }
56
+
57
+
static member FromHash(hash : byte[]) =
58
+
if hash.Length <> 32 then
59
+
failwith "Hash must be 32 bytes (sha2-256)"
60
+
61
+
let cidBytes = Array.zeroCreate<byte> 36
62
+
cidBytes.[0] <- 0x01uy
63
+
cidBytes.[1] <- 0x71uy
64
+
cidBytes.[2] <- 0x12uy
65
+
cidBytes.[3] <- 0x20uy
66
+
Array.Copy(hash, 0, cidBytes, 4, 32)
67
+
Cid cidBytes
68
+
69
+
override this.ToString() =
70
+
"b" + Base32Encoding.ToString(this.Bytes)
+83
PDSharp.Core/DagCbor.fs
+83
PDSharp.Core/DagCbor.fs
···
···
1
+
namespace PDSharp.Core
2
+
3
+
open System
4
+
open System.Collections.Generic
5
+
open System.Formats.Cbor
6
+
open System.IO
7
+
open System.Text
8
+
9
+
module DagCbor =
10
+
type SortKey = { Length : int; Bytes : byte[] }
11
+
12
+
let private getSortKey (key : string) =
13
+
let bytes = Encoding.UTF8.GetBytes(key)
14
+
{ Length = bytes.Length; Bytes = bytes }
15
+
16
+
let private compareKeys (a : string) (b : string) =
17
+
let ka = getSortKey a
18
+
let kb = getSortKey b
19
+
20
+
if ka.Length <> kb.Length then
21
+
ka.Length.CompareTo kb.Length
22
+
else
23
+
let mutable res = 0
24
+
let mutable i = 0
25
+
26
+
while res = 0 && i < ka.Bytes.Length do
27
+
res <- ka.Bytes.[i].CompareTo(kb.Bytes.[i])
28
+
i <- i + 1
29
+
30
+
res
31
+
32
+
let rec private writeItem (writer : CborWriter) (item : obj) =
33
+
match item with
34
+
| null -> writer.WriteNull()
35
+
| :? bool as b -> writer.WriteBoolean(b)
36
+
| :? int as i -> writer.WriteInt32(i)
37
+
| :? int64 as l -> writer.WriteInt64(l)
38
+
| :? string as s -> writer.WriteTextString(s)
39
+
| :? (byte[]) as b -> writer.WriteByteString(b)
40
+
| :? Cid as c ->
41
+
let tag = LanguagePrimitives.EnumOfValue<uint64, CborTag>(42UL)
42
+
writer.WriteTag(tag)
43
+
let rawCid = c.Bytes
44
+
let linkBytes = Array.zeroCreate<byte> (rawCid.Length + 1)
45
+
linkBytes.[0] <- 0x00uy
46
+
Array.Copy(rawCid, 0, linkBytes, 1, rawCid.Length)
47
+
writer.WriteByteString(linkBytes)
48
+
49
+
| :? Map<string, obj> as m ->
50
+
let keys = m |> Map.toList |> List.map fst |> List.sortWith compareKeys
51
+
writer.WriteStartMap(keys.Length)
52
+
53
+
for k in keys do
54
+
writer.WriteTextString(k)
55
+
writeItem writer (m.[k])
56
+
57
+
writer.WriteEndMap()
58
+
59
+
| :? IDictionary<string, obj> as d ->
60
+
let keys = d.Keys |> Seq.toList |> List.sortWith compareKeys
61
+
writer.WriteStartMap(d.Count)
62
+
63
+
for k in keys do
64
+
writer.WriteTextString(k)
65
+
writeItem writer (d.[k])
66
+
67
+
writer.WriteEndMap()
68
+
69
+
| :? seq<obj> as l ->
70
+
let arr = l |> Seq.toArray
71
+
writer.WriteStartArray(arr.Length)
72
+
73
+
for x in arr do
74
+
writeItem writer x
75
+
76
+
writer.WriteEndArray()
77
+
78
+
| _ -> failwith $"Unsupported type for DAG-CBOR: {item.GetType().Name}"
79
+
80
+
let encode (data : obj) : byte[] =
81
+
let writer = new CborWriter(CborConformanceMode.Strict, false, false)
82
+
writeItem writer data
83
+
writer.Encode()
+7
-4
PDSharp.Core/PDSharp.Core.fsproj
+7
-4
PDSharp.Core/PDSharp.Core.fsproj
···
5
</PropertyGroup>
6
7
<ItemGroup>
8
-
<Compile Include="Config.fs"/>
9
-
<Compile Include="Crypto.fs"/>
10
-
<Compile Include="DidResolver.fs"/>
11
-
<Compile Include="Library.fs"/>
12
</ItemGroup>
13
14
<ItemGroup>
15
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
16
</ItemGroup>
17
</Project>
···
5
</PropertyGroup>
6
7
<ItemGroup>
8
+
<Compile Include="Config.fs" />
9
+
<Compile Include="Cid.fs" />
10
+
<Compile Include="DagCbor.fs" />
11
+
<Compile Include="Crypto.fs" />
12
+
<Compile Include="DidResolver.fs" />
13
+
<Compile Include="Library.fs" />
14
</ItemGroup>
15
16
<ItemGroup>
17
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
18
+
<PackageReference Include="System.Formats.Cbor" Version="10.0.1" />
19
</ItemGroup>
20
</Project>
+28
-8
PDSharp.Tests/Tests.fs
+28
-8
PDSharp.Tests/Tests.fs
···
1
๏ปฟmodule Tests
2
3
-
open System
4
open Xunit
5
open PDSharp.Core.Models
6
open PDSharp.Core.Config
7
open PDSharp.Core.Crypto
8
open PDSharp.Core.DidResolver
9
open Org.BouncyCastle.Utilities.Encoders
10
open System.Text
11
open Org.BouncyCastle.Math
12
-
13
-
[<Fact>]
14
-
let ``My test`` () = Assert.True(true)
15
16
[<Fact>]
17
let ``Can instantiate AppConfig`` () =
···
91
}"""
92
93
let doc =
94
-
System.Text.Json.JsonSerializer.Deserialize<DidDocument>(
95
-
json,
96
-
Json.JsonSerializerOptions(PropertyNameCaseInsensitive = true)
97
-
)
98
99
Assert.Equal("did:web:example.com", doc.Id)
100
Assert.Single doc.VerificationMethod |> ignore
101
Assert.Equal("Multikey", doc.VerificationMethod.Head.Type)
···
1
๏ปฟmodule Tests
2
3
open Xunit
4
open PDSharp.Core.Models
5
open PDSharp.Core.Config
6
open PDSharp.Core.Crypto
7
+
open PDSharp.Core
8
open PDSharp.Core.DidResolver
9
open Org.BouncyCastle.Utilities.Encoders
10
open System.Text
11
+
open System.Text.Json
12
open Org.BouncyCastle.Math
13
14
[<Fact>]
15
let ``Can instantiate AppConfig`` () =
···
89
}"""
90
91
let doc =
92
+
JsonSerializer.Deserialize<DidDocument>(json, JsonSerializerOptions(PropertyNameCaseInsensitive = true))
93
94
Assert.Equal("did:web:example.com", doc.Id)
95
Assert.Single doc.VerificationMethod |> ignore
96
Assert.Equal("Multikey", doc.VerificationMethod.Head.Type)
97
+
98
+
[<Fact>]
99
+
let ``CID Generation from Hash`` () =
100
+
let hash =
101
+
Hex.Decode("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
102
+
103
+
let cid = Cid.FromHash hash
104
+
Assert.Equal<byte>(0x01uy, cid.Bytes.[0])
105
+
Assert.Equal<byte>(0x71uy, cid.Bytes.[1])
106
+
Assert.Equal<byte>(0x12uy, cid.Bytes.[2])
107
+
Assert.Equal<byte>(0x20uy, cid.Bytes.[3])
108
+
109
+
[<Fact>]
110
+
let ``DAG-CBOR Canonical Sorting`` () =
111
+
let m = Map.ofList [ ("b", box 1); ("a", box 2) ]
112
+
let encoded = DagCbor.encode m
113
+
let hex = Hex.ToHexString encoded
114
+
Assert.Equal("a2616102616201", hex)
115
+
116
+
[<Fact>]
117
+
let ``DAG-CBOR Sorting Length vs Bytes`` () =
118
+
let m = Map.ofList [ ("aa", box 1); ("b", box 2) ]
119
+
let encoded = DagCbor.encode m
120
+
let hex = Hex.ToHexString encoded
121
+
Assert.Equal("a261620262616101", hex)
+3
-3
roadmap.txt
+3
-3
roadmap.txt
···
17
--------------------------------------------------------------------------------
18
Milestone C: DAG-CBOR + CID
19
--------------------------------------------------------------------------------
20
-
- Canonical DAG-CBOR encode/decode with IPLD link tagging
21
-
- CID creation/parsing (multicodec dag-cbor, sha2-256)
22
DoD: Record JSON โ stable DAG-CBOR bytes โ deterministic CID
23
--------------------------------------------------------------------------------
24
Milestone D: MST Implementation
···
70
Milestone K: DNS + TLS + Reverse Proxy
71
--------------------------------------------------------------------------------
72
- DNS A/AAAA records for PDS hostname
73
-
- TLS certs (ACME) via Caddy/Nginx/Traefik
74
DoD: https://<pds-hostname> responds with valid cert
75
--------------------------------------------------------------------------------
76
Milestone L: Deploy PDSharp
···
17
--------------------------------------------------------------------------------
18
Milestone C: DAG-CBOR + CID
19
--------------------------------------------------------------------------------
20
+
- [x] Canonical DAG-CBOR encode/decode with IPLD link tagging
21
+
- [x] CID creation/parsing (multicodec dag-cbor, sha2-256)
22
DoD: Record JSON โ stable DAG-CBOR bytes โ deterministic CID
23
--------------------------------------------------------------------------------
24
Milestone D: MST Implementation
···
70
Milestone K: DNS + TLS + Reverse Proxy
71
--------------------------------------------------------------------------------
72
- DNS A/AAAA records for PDS hostname
73
+
- TLS certs (ACME) via Caddy
74
DoD: https://<pds-hostname> responds with valid cert
75
--------------------------------------------------------------------------------
76
Milestone L: Deploy PDSharp