an atproto pds written in F# (.NET 9) 馃
pds
fsharp
giraffe
dotnet
atproto
1namespace PDSharp.Core
2
3open System
4
5/// Repository commit signing and management
6module Repository =
7 /// TID (Timestamp ID) generation for revision IDs
8 module Tid =
9 let private chars = "234567abcdefghijklmnopqrstuvwxyz"
10 let private clockIdBits = 10
11 let private timestampBits = 53
12
13 /// Generate a random clock ID component
14 let private randomClockId () =
15 let rng = Random()
16 rng.Next(1 <<< clockIdBits)
17
18 /// Encode a number to base32 sortable string
19 let private encode (value : int64) (length : int) =
20 let mutable v = value
21 let arr = Array.zeroCreate<char> length
22
23 for i in (length - 1) .. -1 .. 0 do
24 arr.[i] <- chars.[int (v &&& 0x1FL)]
25 v <- v >>> 5
26
27 String(arr)
28
29 /// Generate a new TID based on current timestamp
30 let generate () : string =
31 let timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
32 let clockId = randomClockId ()
33 let combined = (timestamp <<< clockIdBits) ||| int64 clockId
34 encode combined 13
35
36 /// Unsigned commit record (before signing)
37 type UnsignedCommit = {
38 Did : string
39 Version : int
40 Data : Cid
41 Rev : string
42 Prev : Cid option
43 }
44
45 /// Signed commit record
46 type SignedCommit = {
47 Did : string
48 Version : int
49 Data : Cid
50 Rev : string
51 Prev : Cid option
52 Sig : byte[]
53 }
54
55 /// Convert unsigned commit to CBOR-encodable map
56 let private unsignedToCborMap (commit : UnsignedCommit) : Map<string, obj> =
57 let baseMap =
58 Map.ofList [
59 ("did", box commit.Did)
60 ("version", box commit.Version)
61 ("data", box commit.Data)
62 ("rev", box commit.Rev)
63 ]
64
65 match commit.Prev with
66 | Some prev -> baseMap |> Map.add "prev" (box prev)
67 | None -> baseMap
68
69 /// Sign an unsigned commit
70 let signCommit (key : Crypto.EcKeyPair) (commit : UnsignedCommit) : SignedCommit =
71 let cborMap = unsignedToCborMap commit
72 let cborBytes = DagCbor.encode cborMap
73 let hash = Crypto.sha256 cborBytes
74 let signature = Crypto.sign key hash
75
76 {
77 Did = commit.Did
78 Version = commit.Version
79 Data = commit.Data
80 Rev = commit.Rev
81 Prev = commit.Prev
82 Sig = signature
83 }
84
85 /// Verify a signed commit's signature
86 let verifyCommit (key : Crypto.EcKeyPair) (commit : SignedCommit) : bool =
87 let unsigned = {
88 Did = commit.Did
89 Version = commit.Version
90 Data = commit.Data
91 Rev = commit.Rev
92 Prev = commit.Prev
93 }
94
95 let cborMap = unsignedToCborMap unsigned
96 let cborBytes = DagCbor.encode cborMap
97 let hash = Crypto.sha256 cborBytes
98 Crypto.verify key hash commit.Sig
99
100 /// Convert signed commit to CBOR-encodable map
101 let signedToCborMap (commit : SignedCommit) : Map<string, obj> =
102 let baseMap =
103 Map.ofList [
104 ("did", box commit.Did)
105 ("version", box commit.Version)
106 ("data", box commit.Data)
107 ("rev", box commit.Rev)
108 ("sig", box commit.Sig)
109 ]
110
111 match commit.Prev with
112 | Some prev -> baseMap |> Map.add "prev" (box prev)
113 | None -> baseMap
114
115 /// Serialize a signed commit to DAG-CBOR bytes
116 let serializeCommit (commit : SignedCommit) : byte[] =
117 signedToCborMap commit |> DagCbor.encode
118
119 /// Get CID for a signed commit
120 let commitCid (commit : SignedCommit) : Cid =
121 let bytes = serializeCommit commit
122 let hash = Crypto.sha256 bytes
123 Cid.FromHash hash