an atproto pds written in F# (.NET 9) 馃
pds fsharp giraffe dotnet atproto
at main 3.4 kB view raw
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