an atproto pds written in F# (.NET 9) 馃
pds
fsharp
giraffe
dotnet
atproto
1锘縨odule Tests
2
3open Xunit
4open PDSharp.Core.Models
5open PDSharp.Core.Config
6open PDSharp.Core.Crypto
7open PDSharp.Core
8open PDSharp.Core.DidResolver
9open Org.BouncyCastle.Utilities.Encoders
10open System.Text
11open System.Text.Json
12open Org.BouncyCastle.Math
13
14[<Fact>]
15let ``Can instantiate AppConfig`` () =
16 let config = {
17 PublicUrl = "https://example.com"
18 DidHost = "did:web:example.com"
19 JwtSecret = "test-secret-key-for-testing-only"
20 }
21
22 Assert.Equal("did:web:example.com", config.DidHost)
23
24[<Fact>]
25let ``CID TryParse roundtrip`` () =
26 let hash = Crypto.sha256Str "test-data"
27 let cid = Cid.FromHash hash
28 let cidStr = cid.ToString()
29
30 match Cid.TryParse cidStr with
31 | Some parsed -> Assert.Equal<byte[]>(cid.Bytes, parsed.Bytes)
32 | None -> Assert.Fail "TryParse should succeed for valid CID"
33
34[<Fact>]
35let ``CID TryParse returns None for invalid`` () =
36 Assert.True(Cid.TryParse("invalid").IsNone)
37 Assert.True(Cid.TryParse("").IsNone)
38 Assert.True(Cid.TryParse("btooshort").IsNone)
39
40[<Fact>]
41let ``Can instantiate DescribeServerResponse`` () =
42 let response = {
43 availableUserDomains = [ "example.com" ]
44 did = "did:web:example.com"
45 inviteCodeRequired = true
46 }
47
48 Assert.Equal("did:web:example.com", response.did)
49 Assert.Equal(1, response.availableUserDomains.Length)
50
51[<Fact>]
52let ``SHA-256 Hashing correct`` () =
53 let input = "hello world"
54 let hash = sha256Str input
55 let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
56 let actual = Hex.ToHexString(hash)
57 Assert.Equal(expected, actual)
58
59[<Fact>]
60let ``ECDSA P-256 Sign and Verify`` () =
61 let keyPair = generateKey P256
62 let data = Encoding.UTF8.GetBytes("test message")
63 let hash = sha256 data
64
65 let signature = sign keyPair hash
66 Assert.True(signature.Length = 64, "Signature should be 64 bytes (R|S)")
67
68 let valid = verify keyPair hash signature
69 Assert.True(valid, "Signature verification failed")
70
71[<Fact>]
72let ``ECDSA K-256 Sign and Verify`` () =
73 let keyPair = generateKey K256
74 let data = Encoding.UTF8.GetBytes("test k256")
75 let hash = sha256 data
76
77 let signature = sign keyPair hash
78 Assert.True(signature.Length = 64, "Signature should be 64 bytes")
79
80 let valid = verify keyPair hash signature
81 Assert.True(valid, "Signature verification failed")
82
83[<Fact>]
84let ``Low-S Enforcement Logic`` () =
85 let n =
86 BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) // secp256k1 N
87
88 let halfN = n.ShiftRight(1)
89 let highS = halfN.Add(BigInteger.One)
90
91 let lowS = enforceLowS highS n
92 Assert.True(lowS.CompareTo halfN <= 0, "S value should be <= N/2")
93 Assert.Equal(n.Subtract highS, lowS)
94
95[<Fact>]
96let ``DidDocument JSON deserialization`` () =
97 let json =
98 """{
99 "id": "did:web:example.com",
100 "verificationMethod": [{
101 "id": "did:web:example.com#atproto",
102 "type": "Multikey",
103 "controller": "did:web:example.com",
104 "publicKeyMultibase": "zQ3sh..."
105 }]
106 }"""
107
108 let doc =
109 JsonSerializer.Deserialize<DidDocument>(json, JsonSerializerOptions(PropertyNameCaseInsensitive = true))
110
111 Assert.Equal("did:web:example.com", doc.Id)
112 Assert.Single doc.VerificationMethod |> ignore
113 Assert.Equal("Multikey", doc.VerificationMethod.Head.Type)
114
115[<Fact>]
116let ``CID Generation from Hash`` () =
117 let hash =
118 Hex.Decode("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
119
120 let cid = Cid.FromHash hash
121 Assert.Equal<byte>(0x01uy, cid.Bytes.[0])
122 Assert.Equal<byte>(0x71uy, cid.Bytes.[1])
123 Assert.Equal<byte>(0x12uy, cid.Bytes.[2])
124 Assert.Equal<byte>(0x20uy, cid.Bytes.[3])
125
126[<Fact>]
127let ``DAG-CBOR Canonical Sorting`` () =
128 let m = Map.ofList [ ("b", box 1); ("a", box 2) ]
129 let encoded = DagCbor.encode m
130 let hex = Hex.ToHexString encoded
131 Assert.Equal("a2616102616201", hex)
132
133[<Fact>]
134let ``DAG-CBOR Sorting Length vs Bytes`` () =
135 let m = Map.ofList [ ("aa", box 1); ("b", box 2) ]
136 let encoded = DagCbor.encode m
137 let hex = Hex.ToHexString encoded
138 Assert.Equal("a261620262616101", hex)