an atproto pds written in F# (.NET 9) 馃
pds
fsharp
giraffe
dotnet
atproto
1namespace PDSharp.Core
2
3open System
4open System.Text
5open Org.BouncyCastle.Crypto.Digests
6open Org.BouncyCastle.Crypto.Signers
7open Org.BouncyCastle.Crypto.Parameters
8open Org.BouncyCastle.Math
9open Org.BouncyCastle.Asn1.X9
10open Org.BouncyCastle.Crypto.Generators
11open Org.BouncyCastle.Security
12open Org.BouncyCastle.Asn1.Sec
13
14module Crypto =
15 let sha256 (data : byte[]) : byte[] =
16 let digest = Sha256Digest()
17 digest.BlockUpdate(data, 0, data.Length)
18 let size = digest.GetDigestSize()
19 let result = Array.zeroCreate<byte> size
20 digest.DoFinal(result, 0) |> ignore
21 result
22
23 let sha256Str (input : string) : byte[] = sha256 (Encoding.UTF8.GetBytes(input))
24
25 type Curve =
26 | P256
27 | K256
28
29 let getCurveParams (curve : Curve) =
30 match curve with
31 | P256 -> ECNamedCurveTable.GetByName("secp256r1")
32 | K256 -> ECNamedCurveTable.GetByName("secp256k1")
33
34 let getDomainParams (curve : Curve) =
35 let ecP = getCurveParams curve
36 ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed())
37
38 type EcKeyPair = {
39 PrivateKey : ECPrivateKeyParameters option
40 PublicKey : ECPublicKeyParameters
41 Curve : Curve
42 }
43
44 let generateKey (curve : Curve) : EcKeyPair =
45 let domainParams = getDomainParams curve
46 let genParam = ECKeyGenerationParameters(domainParams, SecureRandom())
47 let generator = ECKeyPairGenerator()
48 generator.Init(genParam)
49 let pair = generator.GenerateKeyPair()
50
51 {
52 PrivateKey = Some(pair.Private :?> ECPrivateKeyParameters)
53 PublicKey = (pair.Public :?> ECPublicKeyParameters)
54 Curve = curve
55 }
56
57 let enforceLowS (s : BigInteger) (n : BigInteger) : BigInteger =
58 let halfN = n.ShiftRight(1)
59 if s.CompareTo(halfN) > 0 then n.Subtract(s) else s
60
61 let sign (key : EcKeyPair) (digest : byte[]) : byte[] =
62 match key.PrivateKey with
63 | None -> failwith "Private key required for signing"
64 | Some privParams ->
65 let signer = ECDsaSigner()
66 signer.Init(true, privParams)
67 let inputs = digest
68 let signature = signer.GenerateSignature(inputs)
69 let r = signature.[0]
70 let s = signature.[1]
71
72 let n = privParams.Parameters.N
73 let canonicalS = enforceLowS s n
74
75 let to32Bytes (bi : BigInteger) =
76 let bytes = bi.ToByteArrayUnsigned()
77
78 if bytes.Length > 32 then
79 failwith "Signature component too large"
80
81 let padded = Array.zeroCreate<byte> 32
82 Array.Copy(bytes, 0, padded, 32 - bytes.Length, bytes.Length)
83 padded
84
85 let rBytes = to32Bytes r
86 let sBytes = to32Bytes canonicalS
87 Array.append rBytes sBytes
88
89 let verify (key : EcKeyPair) (digest : byte[]) (signature : byte[]) : bool =
90 if signature.Length <> 64 then
91 false
92 else
93 let rBytes = Array.sub signature 0 32
94 let sBytes = Array.sub signature 32 32
95
96 let r = BigInteger(1, rBytes)
97 let s = BigInteger(1, sBytes)
98
99 let domainParams = key.PublicKey.Parameters
100 let n = domainParams.N
101 let halfN = n.ShiftRight(1)
102
103 if s.CompareTo(halfN) > 0 then
104 false
105 else
106 let signer = ECDsaSigner()
107 signer.Init(false, key.PublicKey)
108 signer.VerifySignature(digest, r, s)