Initial commit

This commit is contained in:
Smaug123
2023-02-04 19:55:38 +00:00
commit ef9c792e64
29 changed files with 71565 additions and 0 deletions

12
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fantomas": {
"version": "5.2.0-alpha-008",
"commands": [
"fantomas"
]
}
}
}

41
.editorconfig Normal file
View File

@@ -0,0 +1,41 @@
root=true
[*]
charset=utf-8
end_of_line=crlf
trim_trailing_whitespace=true
insert_final_newline=true
indent_style=space
indent_size=4
# ReSharper properties
resharper_xml_indent_size=2
resharper_xml_max_line_length=100
resharper_xml_tab_width=2
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
indent_style=space
indent_size=2
[*.{fs,fsi}]
fsharp_bar_before_discriminated_union_declaration=true
fsharp_space_before_uppercase_invocation=true
fsharp_space_before_class_constructor=true
fsharp_space_before_member=true
fsharp_space_before_colon=true
fsharp_space_before_semicolon=true
fsharp_multiline_block_brackets_on_same_column=true
fsharp_newline_between_type_definition_and_members=true
fsharp_align_function_signature_to_indentation=true
fsharp_alternative_long_member_definitions=true
fsharp_multi_line_lambda_closing_newline=true
fsharp_experimental_keep_indent_in_branch=true
fsharp_max_value_binding_width=80
fsharp_max_record_width=0
max_line_length=120
end_of_line=lf
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=2
tab_width=2

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
* eol=auto
*.sh text eol=lf

20
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
ignore:
# Target the lowest version of FSharp.Core, for max compat
- dependency-name: "FSharp.Core"

97
.github/workflows/dotnet.yaml vendored Normal file
View File

@@ -0,0 +1,97 @@
name: .NET
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
NUGET_XMLDOC_MODE: ''
DOTNET_MULTILEVEL_LOOKUP: 0
jobs:
build:
strategy:
matrix:
config:
- Release
- Debug
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration ${{matrix.config}}
- name: Test
run: dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}
build-nix:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Nix
uses: cachix/install-nix-action@v18
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Build
run: nix build
check-dotnet-format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Nix
uses: cachix/install-nix-action@v18
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Run Fantomas
run: nix run .#fantomas -- -r --check .
check-nix-format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Nix
uses: cachix/install-nix-action@v18
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Run Alejandra
run: nix develop --command alejandra --check .
linkcheck:
name: Check links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Nix
uses: cachix/install-nix-action@v18
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Run link checker
run: nix develop --command markdown-link-check README.md
all-required-checks-complete:
needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck]
runs-on: ubuntu-latest
steps:
- run: echo "All required checks complete."

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea/
*.user
*.DotSettings
.DS_Store
result
.profile*

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Compile Include="String.fs" />
<Compile Include="Examples.fs" />
<Compile Include="TestMonoalphabetic.fs" />
<Compile Include="TestCaesar.fs" />
<Compile Include="TestAffine.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FsUnit" Version="5.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CipherSuite\CipherSuite.fsproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,69 @@
namespace CipherSuite.Test
/// Examples from the 2022 National Cipher Challenge.
[<RequireQualifiedAccess>]
module Examples =
[<Literal>]
let challenge1A =
"""P DHZ OVWPUN P DVBSK MPUK FVB OLYL. DPAOVBA AOL VMMPJPHS ZBWWVYA VM IVZZ, PA PZ NVPUN AV IL OHYK AV AYHJR QVKPL KVDU HUK P HT NVPUN AV ULLK FVBY OLSW.
P ZAVSL AOL HAAHJOLK UVAL (UVA ZBYL AOLYL PZ H ILAALY DHF AV WBA AOHA NPCLU AOHA AOPZ VWLYHAPVU PZ ZAYPJASF VMM AOL IVVRZ) MYVT SFUU MYHUR'Z VMMPJL, HUK P AOPUR PA PZ MYVT QVKPL. AOL DVYK ULPNOIVBYOVVK ZBNNLZAZ PA DHZ DYPAALU IF H UHAPCL LUNSPZO ZWLHRLY YHAOLY AOHU HU HTLYPJHU, HUK PA PZ ZPNULK VMM JPMYHY HNHPU. P JHSSLK AOHA HU HSPIP ILMVYL, IBA THFIL UVT KL NBLYYL PZ TVYL HWWYVWYPHAL.
P ULLK APTL AV KLJPWOLY AOL YLWSF, IBA LCLU AOL WHWLY PA PZ DYPAALU VU PZ H JSBL. PA PZ AOL ZVYA FVB MPUK PU AOL MHUJF OVALSZ, ZV P DPSS ZAHYA SVVRPUN AOLYL. AOLF HYL UVA YLHSSF QVKPL'Z ZAFSL, IBA PM ZOL PZ MYLLSHUJPUN MVY IHURZ, ZOL DPSS DHUA AV NPCL AOL YPNOA PTWYLZZPVU. DOPJO NPCLZ TL HU PKLH. P AOPUR P DPSS AHRL H TBJO JSVZLY SVVR HA AOHA SLAALY.
"""
[<Literal>]
let challenge1B =
"""WXTK FL YKTGD,
BM PTL ZKXTM FXXMBGZ RHN TGW B TF IEXTLXW MH TVVXIM MAX VHGMKTVM MH BGOXLMBZTMX MAX LMKTGZX VBKVNFLMTGVXL TM VNEIXIXK VHNGMR UTGD.
TL WBLVNLLXW, B PBEE GXXW YNEE TVVXLL MH MAX UTGD MKTGLTVMBHG KXVHKWL, MAX VNKKXGM TGW KXVXGM LMTYY YBEXL, TGW TGR LNKOXBEETGVX YHHMTZX MATM RHN VTG IKHOBWX, XBMAXK YKHF MAX UTGD, MAX LMKXXML HNMLBWX HK YKHF GXBZAUHNKAHHW LMHKXL.
B XQTFBGXW MAX UHQ MATM RHN YHNGW HG MAX YEHHK HY MAX OTNEM TGW GXXW MH VHKKXVM RHN HG HGX MABGZ. BM PTL GHM XFIMR. MAX BGLBWX HY MAX EBW ATW T LAXXM HY OXEENF ITIXK EBZAMER YBQXW MH BM TGW PAXG B KXFHOXW MATM B YHNGW GBGX VABGXLX VATKTVMXKL BGLVKBUXW BG T LJNTKX ITMMXKG HG MAX KXOXKLX. B MKTVXW MAXF HGMH MAX TMMTVAXW VHOXK LAXXM TGW PHNEW UX ZKTMXYNE BY RHN VHNEW TLD MAX UTGD HYYBVBTEL PAXMAXK MAXR KXVHZGBLX MAXF. BM BL IHLLBUEX MATM MAX ITIXK PTL ZENXW BG PAXG MAX UHQ PTL FTWX, BM PTL VXKMTBGER BGMXGWXW MH EHHD EBDX MATM, UNM ZBOXG MAX TULXGVX HY TGR HMAXK VENXL TM MABL IHBGM B MABGD BM BL PHKMA VAXVDBGZ BY MABL VHNEW ATOX TGR LBZGBYBVTGVX.
RHN VTG VHGMTVM FX TM MAX KV; B PBEE UX FHOBGZ MAXKX MABL TYMXKGHHG TGW MAXR TKX XQIXVMBGZ T VTEE YKHF RHN MH TKKTGZX MAX XQIXGLXL YTVBEBMR MATM RHN HYYXKXW.
B EHHD YHKPTKW MH RHNK KXIER.
CTWX VBYKTK"""
[<Literal>]
let challenge2A =
"K EWJWAQL NO NDWGQ PWLQ/POLKQ NO W ISKNQ WN W FKAF-GZWII FONQZ LOCJNOCJ. NFQ IODN OV TZWGQ OJZM W BWJU GOSZL WVVODL, WJL K GOSZLJN INWM NFQDQ EMIQZV, QXQJ KV K CWI OJ QHTQJIQI. K NFKJU KN COSZL FWXQ BQQJ NQETNKJA VWNQ WJMCWM. KV K GWJ VKJL POLKQ, K WE ISDQ IFQ GWJ VKJL EQ. IOEQNFKJA KJ FQD ZQNNQD NQZZI EQ IFQ WZDQWLM FWI. KJ WJM GWIQ, K LQGKLQL NO UQQT W ZOJA NWKZ OJ FQD, KV OJZM NO WXOKL ITOOUKJA WJMOJQ QZIQ CFO EKAFN BQ VOZZOCKJA FQD. K WE VOGSIKJA OJ KJNQDGQTNKJA FQD GOEEI CKNF ZMJJ VDWJU. NFQM WDQ INKZZ SIKJA VWKDZM ZKAFN QJGDMTNKOJ, WJL KN KI QWIKQD NO AQN WGGQII NO EI VDWJUI OVVKGQ NFWJ KN COSZL BQ NO KJVKZNDWNQ POLKQI DOOE WN NFQ FONQZ. NFOIQ TZWGQI NWUQ NFQ IQGSDKNM OV NFQKD GZKQJNI QXQJ EODQ IQDKOSIZM NFWJ NFQ BWJUI. NFQ WNNWGFQL ZQNNQD IFOCI NFWN POLKQ KI AQNNKJA EODQ GWSNKOSI, BSN KN WZIO IQQEI NO BQ WJ KJXKNWNKOJ NO AQN KJXOZXQL. K WE JON ISDQ CFQDQ NO AO VDOE FQDQ. WI POLKQ IWMI KJ NFQ ZQNNQD, NFQ JSEBQDI KJ NFQ IKAJ-KJ BOOU ISAAQIN IOEQNFKJA OLL, BSN SJZKUQ POLKQ K COJN FWXQ WGGQII NO NFQ XWSZN NO GFQGU KN. KI IFQ FKJNKJA NFWN CQ IFOSZL NQWE ST, OD CWDJKJA EQ NFWN IFQ UJOCI K WE FQDQ WJL IFOSZL UQQT WCWM? (KV MOS WDQ JON ISDQ CFWN K WE NWZUKJA WBOSN, NWUQ W XQDM GWDQVSZ ZOOU WN MOSD LQGDMTN OV FQD ZQNNQD. MOS IFOSZL VKJL W FKLLQJ EQIIWAQ.) BM NFQ CWM, NFQDQ KI IOEQNFKJA JWAAKJA WN EQ NFWN K GWJN YSKNQ TZWGQ. NFQDQ KI W JWEQ KJ NFQ IKAJ-KJ BOOU NFWN, KJ EM FQWL WN ZQWIN, KI GOJJQGNQL KJ IOEQ CWM NO NFQ JWEQ OV NFQ BWJU, WJL K GWJN DQEQEBQD CFWN NFWN GOJJQGNKOJ KI. EWMBQ KN KI JON KETODNWJN, BSN K COJN IZQQT TDOTQDZM SJNKZ K GWJ VKASDQ KN OSN. OJGQ MOS FWXQ GDWGUQL POLKQI ZQNNQD, FQWL OXQD NO NFQ GWIQ VKZQI WJL NWUQ W ZOOU WN NFQ IKAJWNSDQI. EWMBQ MOS GWJ VKASDQ KN OSN VOD EQ. FWDDM"
[<Literal>]
let challenge2B =
"""GPFC JL YCFSR,
QBKP NBD QFG F HBBG VPPRPSG. UQFSR NBD YBC UQP LFYPUN GPKBLZU OBE FXXPLL CPXBCGL NBD LPSU, UQBDHQ XFS Z LDHHPLU UQFU ZS UQP YDUDCP VP DLP F QZHQPC APMPA BY LPXDCZUN? PMPS YFZCAN ZSPEKPCZPSXPG XCZJZSFAL XFS OP YFJZAZFC VZUQ UQPLP XZKQPCL, FSG VP GBSU RSBV VQB JZHQU OP JBSZUBCZSH NBDC XBJJDSZXFUZBS XQFSSPAL.
FL KCBJZLPG, Z PEFJZSPG UQP MFDAU CPXBCGL ZS GPUFZA, FSG UQPN GB UPAA F LUBCN, UQBDHQ Z FJ SBU LDCP VQFU ZU JPFSL. UQPCP ZL F KFUUPCS ZS UQP SDJOPCL UQFU ZL MPCN LDHHPLUZMP FSG ZY NBD XFS PEKPGZUP JN CPTDPLU UB LKPSG LBJP UZJP FABSP ZS UQP MFDAU UQPS Z QBKP UB OP FOAP UB ZSMPLUZHFUP UQFU YDCUQPC.
CP-PEFJZSZSH UQP MFDAU XFJPCF YBBUFHP UQFU NBD LPSU, Z VFL LDCKCZLPG UB LPP UQFU XDAKPKPC QFL SB FXUDFA YBBUFHP BY UQP ABOON BC MFDAU ZULPAY. NBDC LPXDCZUN UPFJ PEKAFZSPG UQFU UQZL ZL F KCZMFXN KBAZXN UB PSLDCP XAZPSU XBSYZGPSUZFAZUN. ZU FALB LPPJL UB OP UQP XFLP UQFU VQZAP XDLUBJPCL LZHS ZS FSG BDU BY UQP MFDAU, OFSR LUFYY FCP SBU CPTDZCPG UB GB LB. UQPN UBAG JP UQFU LUFYY GB SBU ZS HPSPCFA PSUPC UQP ZSSPC XQFJOPC, FSG UQFU SB-BSP LQBDAG PSUPC UQP ABOON DSFXXBJKFSZPG VQZAP UQP MFDAU ZL DSABXRPG. UQP LUFYY LFN UQFU UQZL KCBUBXBA ZL UFRPS MPCN LPCZBDLAN; ZU ZL F LFXRFOAP BYYPSLP UB PSUPC UQP ABOON FABSP. UQPCP ZL BSP PEXPKUZBS. ZS BCGPC UB PSLDCP UQP LPXDCZUN BY UQP XBJOZSFUZBS UQFU BKPSL UQP MFDAU ZULPAY, UQP XQZPY UPAAPC PSUPCL UQP ABOON FABSP ZS UQP JBCSZSH UB BKPS DK. UQP QPFG BY LPXDCZUN VFZUL BDULZGP, UQPS XBSGDXUL F LPFCXQ BY UQP UPAAPC VQPS UQPN APFMP UQP MFDAU UB PSLDCP UQFU SBUQZSH QFL OPPS CPJBMPG. UQP YBBUFHP LQBVL UQP KFU GBVS ZS KCBHCPLL UQFU GFN, LB Z UQZSR ZU ZL YFZC UB GPGDXP UQFU ZY UQP UPAAPC QFG CPJBMPG FSNUQZSH YCBJ UQP LFYP UQPS ZU VBDAG QFMP OPPS GZLXBMPCPG FU UQP UZJP. UQFU JPFSL UQFU ZY FSNUQZSH VFL UFRPS YCBJ UQP OBE, UQPS ZU QFG FACPFGN OPPS CPJBMPG VQPS UQP UPAAPC FSG UQP LPXDCZUN HDFCG FCCZMPG.
CPHFCGZSH UQP KFKPC AZSZSH ZS UQP GPKBLZU OBE AZG: Z FLRPG UQP OFSR LUFYY FOBDU ZU FSG UQP XQZPY UPAAPC, VQB ZL F JFSGFCZS LKPFRPC, CPXBHSZLPG UQP XQFCFXUPCL FL UQP GZHZUL WPCB FSG BSP. LQP LDHHPLUPG UQFU UQP UQCPP AZSPL XBDAG GPSBUP UQP SDJOPCL YZMP, YBDC, UQCPP ZS OZSFCN, UQBDHQ BUQPC CPFGZSHL FCP KBLLZOAP. ZY UQPN VPCP VCZUUPS XAFLLZXFAAN, YBC PEFJKAP UB OP CPFG MPCUZXFAAN CZHQU UB APYU, UQPS UQPN XBDAG GPSBUP LZE, BSP, YZMP. Z VBSGPCPG ZY UQZL CPYPCCPG UB UQP OBE SDJOPC, FL UQPCP FCP LPMPS QDSGCPG FSG LZEUN-UQCPP OBEPL ZS UQP MFDAU, ODU UQP OBE ZULPAY VFL FKKFCPSUAN UFRPS YCBJ XFLLPUUP UVPSUN-UQCPP, LB DSAPLL ZU QFL OPPS KDU OFXR ZS UQP VCBSH KAFXP LBJP UZJP UQPS Z GBSU UQZSR UQFU XFS OP ZU. KPCQFKL LBJPBSP FU UQP OFSR JZHQU QFMP FS ZGPF. APU JP RSBV VQPS NBD QFMP FCCFSHPG UQP MFDAU FXXPLL YBC JP.
NBDCL LZSXPCPAN,
IFGP XZYCFC"""
[<Literal>]
let challenge3A =
"RWLQM QAUIS QVOBP QADMZ GMIAG NWZUM PMZZM KMVBK IAMVW BMAEM ZMTMN BWVIV MVKZG XBMLC AJSMG IBBIK PMLBW PMZSM GKPIQ VQPIL BISMV BPMAC QBMWX XWAQB MIVLZ MDMZA MLBPM AXGPW TMQVU GLWWZ AWQKW CTLEI BKPPM ZUWDM UMVBA QBPWC OPBQP ILJMM VKIZM NCTJC BQAEM IZBPI BAPMO TIVKM LWDMZ IBUMI AAPMT WKSML BPMLW WZBWP MZZWW UBPMV APMEI TSMLI EIGLW EVBPM KWZZQ LWZTM IDQVO BPMSM GAQVB PMTWK SQEWV LMZML QNQAP WCTLK ITTWC BBWPM ZJCBX MZPIX AAPME IAKPM KSQVO BPIBQ EIAVW BBPMZ MJMNW ZMTMI DQVOB PMSMG AZIBP MZBPI VBZGQ VOBWU ISMAC ZMQEI ACVBQ TQKIV JMKMZ BIQVB PIBAP MQAZM IKPQV OWCBQ EQTTP IDMBW JMLQA KZMMB BPMMV KZGXB QWVWV BPMCA JQAMI AGBWJ ZMISC AQVOI AQUXT MSMGE WZLAC JABQB CBQWV QBKWV BIQVA RCABW VMNQT MEQBP VWBMA WVPMZ QVDMA BQOIB QWVIV LAPMP IAKPI VVMTT MLPMZ QVVMZ APMZT WKSMT QUQVI BQVOB PMQUX WAAQJ TMBWI ZZQDM IBBPM WVTGT WOQKI TKWVK TCAQW VQVAP WZBAW UMWVM PIAIA MKZMB IVLAW UMWVM MTAMB PMKPQ MNBMT TMZEI VBABW SVWEE PIBQB QABPM GBPQV SRWLQ MKIVI AAQAB IVLUI GJMRW LQMQA BZGQV OBWBM TTUMI JWCBQ BQLWV BSVWE QNBPI BQAJM KICAM APMBP QVSAA PMVMM LAUGP MTXQI UXZMB BGACZ MAPML WMAVB WZQNA PMBPQ VSAQV MMLBW SVWEI JWCBQ BJCBC VBQTE MKIVN QVLIE IGBWZ MMABI JTQAP BZCAB QBQAO WQVOB WJMLQ NNQKC TBBWN QVLWC BEPQK P"
[<Literal>]
let challenge3B =
"""ZBGOF BZ GQO RZIOFGRPWGRBZ
GQO TQRON GOXXOE OZGOEOS GQO IWHXG WTTBYCWZROS AL GQO QOWS BN FOTHERGL GQO ZRPQG AONBEO GQO RZTRSOZG GB TQOTV GQWG GQO SOCBFRG ABKOF JOEO FOTHEO WZS GB XBTV GQO IWHXG SBBE. WXX JWF RZ BESOE WG GQWG FGWPO.
GQO FOTHERGL NBBGWPO FQBJF GQWG ZB-BZO WZS ZBGQRZP XONG GQO IWHXG BIOEZRPQG. GQO TWYOEWF JOEO YBZRGBEOS XRIO NEBY W EOYBGO XBTWGRBZ, WZS R QWIO EOIROJOS GQO NBBGWPO YLFOXN.
WNGOE GQO GOXXOE XONG GQO IWHXG GB EWRFO GQO WXWEY, FQO WZS GQO FOTHERGL PHWES JOEO BZ TWYOEW HZGRX WSSRGRBZWX FOTHERGL WEERIOS WZS ZORGQOE BN GQOY QWS WZLGQRZP RZ GQORE CBFFOFFRBZ GQWG GQOL TBHXS ZBG WTTBHZG NBE.
GQO SOCBFRG ABKOF WEO WXX OYCGL, OKTOCG GQWG GQOL OWTQ TBZGWRZ W CWCOE XRZRZP RZ GQO XRS YWEVOS JRGQ ZRZO TQRZOFO TQWEWTGOEF EOCEOFOZGRZP GQO SRPRGF BZO WZS MOEB, FBYO JRGQ GQO PERS RZFRSO WZ BHGXRZO WZS FBYO JRGQBHG. GQO YWEVRZPF WEO HZRDHO GB OWTQ ABK.
TBZTXHFRBZ: GQO AWZV IWHXG JWF FOTHEO HZGRX BCOZOS RZ GQO YBEZRZP WZS ZBGQRZP JWF EOYBIOS RZ GQWG COERBS, FB JO TWZ EHXO BHG GQONG WF W YBGRIO NBE GQO AEOWV RZ. GQO BZXL COEFBZ JQB TBHXS QWIO SRFGHEAOS GQO IWHXG TBZGOZGF JWF GQO TQRON GOXXOE, AHG FQO ZBG BZXL NWRXOS GB TBZTOWX GQO SRFGHEAWZTO, FQO WXFB ZBGRNROS FOTHERGL WABHG RG. TXOWEXL, FQO JWZGOS WZ RZIOFGRPWGRBZ, AHG SRS ZBG JWZG GQO CBXRTO RZIBXIOS. R JWF AEBHPQG RZ WG PEOWG OKCOZFO OIOZ GQBHPQ THXCOCOE WXEOWSL OYCXBLF RGF BJZ FOTHERGL WZS RZIOFGRPWGRBZ GOWY JQRTQ QWF YWZL BN GQO FWYO FVRXXF WF R SB. GQOEO RF COEQWCF UHFG BZO GQRZP R TWZ SB AOGGOE GQWZ GQOY WZS GQWG RF GB AEOWV TBSOF WZS TRCQOEF. YWLAO GQWG RF JQL R WY QOEO. R AOXROIO GQWG GQO TQRON GOXXOE SOXRAOEWGOXL FGWPOS GQO AEOWV RZ GB QBBV YO, FB R TBHXS SRFTBIOE WZS AEOWV W TBSO QRSSOZ RZ GQO IWHXG. JQWG R SBZG LOG VZBJ RF, JQL?
R SBZG GEHFG GQO TQRON GOXXOE, GQBHPQ R SBZG VZBJ JQWG FQO RF HC GB, WZS R WY ZBG FHEO GQWG R TWZ GEHFG XLZZ NEWZV, AHG WFFHYRZP GQWG R WY ERPQG GQOL ZOOS YO YBEO GQWZ R ZOOS GQOY, FB R FQBHXS AO FWNO NBE ZBJ JQRXO R NBTHF BZ GQO THXCOCOE TBSO."""

View File

@@ -0,0 +1,13 @@
namespace CipherSuite.Test
[<RequireQualifiedAccess>]
module String =
let stripNonAlpha (s : string) =
s.ToCharArray ()
|> Array.choose (fun c ->
let c = System.Char.ToUpper c
let shifted = int c - 65
if shifted >= 0 && shifted < 26 then Some c else None
)
|> System.String

View File

@@ -0,0 +1,48 @@
namespace CipherSuite.Test
open NUnit.Framework
open CipherSuite
open FsUnitTyped
[<TestFixture>]
module TestAffine =
[<Test>]
let ``Examples, Challenge 1`` () =
let key, plain, fitness = Affine.crack Examples.challenge1A
plain.StartsWith "I WAS HOPING I WOULD FIND YOU HERE" |> shouldEqual true
// Note: this is the inverse of the correct key
key |> shouldEqual (1, 19)
let key, plain, fitness = Affine.crack Examples.challenge1B
plain.StartsWith "DEAR MS FRANK," |> shouldEqual true
key |> shouldEqual (1, 7)
[<Test>]
let ``Examples, Challenge 2`` () =
let key, plain, fitness = Affine.crack Examples.challenge2A
plain.StartsWith "I MANAGED TO TRACE JADE/JODIE TO A SUITE" |> shouldEqual true
// Note: this is the inverse of the correct key
key |> shouldEqual (21, 6)
let key, plain, fitness = Affine.crack Examples.challenge2B
plain.StartsWith "DEAR MS FRANK," |> shouldEqual true
key |> shouldEqual (3, 11)
[<Test>]
let ``Examples, Challenge 3`` () =
let key, plain, fitness = Affine.crack Examples.challenge3A
plain.StartsWith "JODIE ISMAK INGTH ISVER YEASY FORME HERRE CENTC"
|> shouldEqual true
// Note: this is the inverse of the correct key
key |> shouldEqual (1, 18)

View File

@@ -0,0 +1,23 @@
namespace CipherSuite.Test
open NUnit.Framework
open CipherSuite
open FsUnitTyped
[<TestFixture>]
module TestCaesar =
[<Test>]
let ``Examples, Challenge 1`` () =
let key, plain, fitness = Caesar.crack Examples.challenge1A
plain.StartsWith "I WAS HOPING I WOULD FIND YOU HERE" |> shouldEqual true
printfn "%f" fitness
key |> shouldEqual 6
let key, plain, fitness = Caesar.crack Examples.challenge1B
plain.StartsWith "DEAR MS FRANK," |> shouldEqual true
key |> shouldEqual 18

View File

@@ -0,0 +1,17 @@
namespace CipherSuite.Test
open CipherSuite
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestMonoalphabetic =
[<Test>]
let ``Challenge 1A`` () =
let rand = System.Random 10
let key, _plain, _fitness =
Monoalphabetic.crack rand 20000 (String.stripNonAlpha Examples.challenge1A)
key |> shouldEqual [| yield! [| 19..25 |] ; yield! [| 0..18 |] |]

22
CipherSuite.sln Normal file
View File

@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CipherSuite", "CipherSuite\CipherSuite.fsproj", "{2D7FFCCE-91B8-4EA0-9FA1-52D95AB88C2B}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CipherSuite.Test", "CipherSuite.Test\CipherSuite.Test.fsproj", "{42EBED52-DFE2-4DAC-BE31-87DCA6EC69BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2D7FFCCE-91B8-4EA0-9FA1-52D95AB88C2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D7FFCCE-91B8-4EA0-9FA1-52D95AB88C2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D7FFCCE-91B8-4EA0-9FA1-52D95AB88C2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D7FFCCE-91B8-4EA0-9FA1-52D95AB88C2B}.Release|Any CPU.Build.0 = Release|Any CPU
{42EBED52-DFE2-4DAC-BE31-87DCA6EC69BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42EBED52-DFE2-4DAC-BE31-87DCA6EC69BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42EBED52-DFE2-4DAC-BE31-87DCA6EC69BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42EBED52-DFE2-4DAC-BE31-87DCA6EC69BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

59
CipherSuite/Affine.fs Normal file
View File

@@ -0,0 +1,59 @@
namespace CipherSuite
#if DEBUG
#else
#nowarn "9"
#endif
open System.Text
[<RequireQualifiedAccess>]
module Affine =
let private hcf (i : int) (j : int) =
let rec go (bigger : int) (smaller : int) =
if smaller = 0 then
bigger
else
go smaller (bigger % smaller)
if i < 0 || j < 0 then
failwithf "HCF only implemented for positive numbers, got %i %i" i j
go (max i j) (min i j)
/// multiplier must be coprime to 26.
let encrypt (multiplier : int) (add : int) (s : string) : string =
if hcf multiplier 26 <> 1 then
failwithf "multiplier must be coprime to 26, got %i" multiplier
let sb = StringBuilder ()
for c in s do
if 'A' <= c && c <= 'Z' then
sb.Append (((int c - int 'A') * multiplier + add) % 26 + int 'A' |> char)
elif 'a' <= c && c <= 'z' then
sb.Append (((int c - int 'a') * multiplier + add) % 26 + int 'a' |> char)
else
sb.Append c
|> ignore
sb.ToString ()
/// Returns the inverse of the correct key, because I'm lazy.
let crack (s : string) =
#if DEBUG
let fitness = Fitness.fromEmbedded ()
#else
let arr = Fitness.allocate ()
use ptr = fixed arr
let fitness = Fitness.fromEmbedded ptr
#endif
seq {
for mul in 1..2..25 do
if mul <> 13 then
for add in 0..25 do
let encrypt = encrypt mul add s
yield (mul, add), encrypt, Fitness.get encrypt fitness
}
|> Seq.maxBy (fun (_, _, fitness) -> fitness)

135
CipherSuite/Arr4D.fs Normal file
View File

@@ -0,0 +1,135 @@
namespace CipherSuite
#if DEBUG
#else
#nowarn "9"
#endif
open Microsoft.FSharp.NativeInterop
[<Struct>]
#if DEBUG
type Arr4D<'a> =
{
Elements : 'a array
Width : int
WidthTimesHeight : int
WidthTimesHeightTimesDepth : int
}
member this.Depth = this.Elements.Length / this.WidthTimesHeightTimesDepth
#else
type Arr4D<'a when 'a : unmanaged> =
{
Elements : nativeptr<'a>
Length : int
Width : int
WidthTimesHeight : int
WidthTimesHeightTimesDepth : int
}
member this.Depth = this.Length / this.WidthTimesHeightTimesDepth
#endif
[<RequireQualifiedAccess>]
module Arr4D =
/// It's faster to iterate forward over the first argument, `x`, and then the
/// second argument, `y`, and then the third argument, `z`.
let inline get (arr : Arr4D<'a>) (x : int) (y : int) (z : int) (i : int) : 'a =
#if DEBUG
arr.Elements.[i * arr.WidthTimesHeightTimesDepth
+ z * arr.WidthTimesHeight
+ y * arr.Width
+ x]
#else
NativePtr.get
arr.Elements
(i * arr.WidthTimesHeightTimesDepth
+ z * arr.WidthTimesHeight
+ y * arr.Width
+ x)
#endif
let inline set (arr : Arr4D<'a>) (x : int) (y : int) (z : int) (i : int) (newVal : 'a) : unit =
#if DEBUG
arr.Elements.[i * arr.WidthTimesHeightTimesDepth
+ z * arr.WidthTimesHeight
+ y * arr.Width
+ x] <- newVal
#else
NativePtr.write
(NativePtr.add
arr.Elements
(i * arr.WidthTimesHeightTimesDepth
+ z * arr.WidthTimesHeight
+ y * arr.Width
+ x))
newVal
#endif
#if DEBUG
/// In lieu of a meaningful name for the ana-kata axis, I am just going to call it katana.
let create (width : int) (height : int) (depth : int) (katana : int) (value : 'a) : Arr4D<'a> =
let arr = Array.create (width * height * depth * katana) value
{
Width = width
WidthTimesHeight = width * height
WidthTimesHeightTimesDepth = width * height * depth
Elements = arr
}
#else
/// In lieu of a meaningful name for the ana-kata axis, I am just going to call it katana.
/// The input array must be at least of size width * height * depth * katana.
let create
(arr : nativeptr<'a>)
(width : int)
(height : int)
(depth : int)
(katana : int)
(value : 'a)
: Arr4D<'a>
=
{
Width = width
Elements = arr
Length = width * height * depth * katana
WidthTimesHeight = width * height
WidthTimesHeightTimesDepth = width * height * depth
}
#endif
[<RequiresExplicitTypeArguments>]
#if DEBUG
let zeroCreate<'a when 'a : unmanaged> (width : int) (height : int) (depth : int) (katana : int) : Arr4D<'a> =
{
Elements = Array.zeroCreate (width * height * depth * katana)
Width = width
WidthTimesHeight = width * height
WidthTimesHeightTimesDepth = width * height * depth
}
#else
let zeroCreate<'a when 'a : unmanaged>
(elts : nativeptr<'a>)
(width : int)
(height : int)
(depth : int)
(katana : int)
: Arr4D<'a>
=
{
Elements = elts
Width = width
WidthTimesHeight = width * height
WidthTimesHeightTimesDepth = width * height * depth
Length = width * height * depth * katana
}
#endif
let inline clear (a : Arr4D<'a>) : unit =
#if DEBUG
System.Array.Clear a.Elements
#else
NativePtr.initBlock a.Elements 0uy (uint32 sizeof<'a> * uint32 a.Length)
#endif

22
CipherSuite/Assembly.fs Normal file
View File

@@ -0,0 +1,22 @@
namespace CipherSuite
open System.IO
[<RequireQualifiedAccess>]
module internal Assembly =
let loadResource (name : string) =
let assembly = System.Reflection.Assembly.GetExecutingAssembly ()
if not (assembly.GetManifestResourceNames () |> Array.contains name) then
assembly.GetManifestResourceNames ()
|> String.concat ", "
|> failwithf "bad name %s, had: %s" name
seq {
use sr = new StreamReader (assembly.GetManifestResourceStream name)
let mutable line = sr.ReadLine ()
while not (obj.ReferenceEquals (null, line)) do
yield line
line <- sr.ReadLine ()
}

38
CipherSuite/Caesar.fs Normal file
View File

@@ -0,0 +1,38 @@
namespace CipherSuite
#if DEBUG
#else
#nowarn "9"
#endif
[<RequireQualifiedAccess>]
module Caesar =
let shift (s : string) (i : int) : string =
Array.init
s.Length
(fun index ->
let c = s.[index]
if 'A' <= c && c <= 'Z' then
char ((int c - int 'A' + i) % 26 + int 'A')
elif 'a' <= c && c <= 'z' then
char ((int c - int 'a' + i) % 26 + int 'a')
else
c
)
|> System.String
let crack (s : string) =
#if DEBUG
let fitness = Fitness.fromEmbedded ()
#else
let arr = Fitness.allocate ()
use ptr = fixed arr
let fitness = Fitness.fromEmbedded ptr
#endif
seq { 0..25 }
|> Seq.map (fun i -> 25 - i, shift s i)
|> Seq.map (fun (shift, text) -> (shift, text, Fitness.get text fitness))
|> Seq.maxBy (fun (_, _, fitness) -> fitness)

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="Arr4D.fs" />
<Compile Include="Assembly.fs" />
<Compile Include="FitnessAnalyser.fs" />
<Compile Include="Caesar.fs" />
<Compile Include="Affine.fs" />
<Compile Include="Monoalphabetic.fs" />
<EmbeddedResource Include="tetragraphs.txt" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,83 @@
namespace CipherSuite
open System
type Fitness =
private
{
Quadruplets : Arr4D<int>
Total : int
}
[<RequireQualifiedAccess>]
module Fitness =
let inline private toIndex (c : char) : int = int (System.Char.ToUpper c) - 65
#if DEBUG
let fromEmbedded () : Fitness =
let frequencies = Arr4D.zeroCreate<_> 26 26 26 26
#else
let allocate () =
Array.zeroCreate<int> (26 * 26 * 26 * 26)
/// Pass in a pointer to an array of length 26*26*26*26, e.g. a pointer to
/// the output of `allocate`.
let fromEmbedded (arr : nativeptr<int>) : Fitness =
let frequencies = Arr4D.zeroCreate<_> arr 26 26 26 26
#endif
let tetragraphs = Assembly.loadResource "CipherSuite.tetragraphs.txt"
let mutable total = 0
for line in tetragraphs do
if line.[0] <> '#' then
let line = line.AsSpan ()
let i = Int32.Parse (line.Slice (line.IndexOf (' ') + 1))
total <- total + i
let w = toIndex line.[0]
let x = toIndex line.[1]
let y = toIndex line.[2]
let z = toIndex line.[3]
if w < 0 || x < 0 || y < 0 || z < 0 then
failwith "negative"
if w > 25 || x > 25 || y > 25 || z > 25 then
failwith "too big"
Arr4D.set frequencies w x y z i
{
Quadruplets = frequencies
Total = total
}
/// Assumes the input is characters 0..25, where "a" is 0.
/// This fitness can meaningfully be compared across strings
/// with different lengths.
let get' (s : int[]) (f : Fitness) : float =
let mutable score = 0
for j = 0 to s.Length - 4 do
let first = s.[j]
let second = s.[j + 1]
let third = s.[j + 2]
let fourth = s.[j + 3]
score <- score + Arr4D.get f.Quadruplets first second third fourth
float score / float s.Length
let get (s : string) (f : Fitness) : float =
let s =
s.ToUpper().ToCharArray ()
|> Array.choose (fun c ->
if 'A' <= c && c <= 'Z' then
Some (int c - int 'A')
else
None
)
get' s f

View File

@@ -0,0 +1,118 @@
namespace CipherSuite
#if DEBUG
#else
#nowarn "9"
#endif
[<RequireQualifiedAccess>]
module Monoalphabetic =
/// The hill climber is allowed to accept a worse solution sometimes
/// (in the name of escaping local optima). This is a table of probabilities
/// for how much worse the solution is allowed to be; for example, there is a
/// 5 in 20 chance that we don't accept any worse solution, and a 2 in 20 chance
/// we accept a solution that is 8-or-more worse.
/// I have completely lost the source of these numbers, and it appears that
/// by sheer coincidence the scale I chose for the built-in fitness function
/// happens to be very compatible with these perturbations.
let private perturbations =
[|
0
0
0
0
0
1
1
1
1
1
2
2
2
3
4
4
5
6
8
15
|]
|> Array.map float
let private decrypt (key : int[]) (ciphertext : int[]) (output : int[]) : unit =
for i = 0 to ciphertext.Length - 1 do
output.[i] <- key.[ciphertext.[i]]
let inline private swap (arr : int[]) (p1 : int) (p2 : int) : unit =
let temp = arr.[p1]
arr.[p1] <- arr.[p2]
arr.[p2] <- temp
let rec private backToString (text : int[]) : string =
text |> Array.map ((+) (int 'a') >> char) |> System.String
// I *think* this may originally be an algorithm described in
// http://web.archive.org/web/20110210123730/http://web.mac.com/mikejcowan/Ciphers/Churn_Algorithm.html
// But it was over a decade ago and I've simply forgotten the source.
let crack (rand : System.Random) (keysLimit : int) (ciphertext : string) : int[] * string * float =
let ciphertext = ciphertext.ToUpperInvariant ()
#if DEBUG
let fitness = Fitness.fromEmbedded ()
#else
let arr = Fitness.allocate ()
use ptr = fixed arr
let fitness = Fitness.fromEmbedded ptr
#endif
let plaintext =
Array.init
ciphertext.Length
(fun i ->
let output = int ciphertext.[i] - int 'A'
if output < 0 || output > 25 then
failwith "non-alphabetic character"
output
)
let ciphertext = Array.copy plaintext
let currentKey = [| 0..25 |]
let bestKey = Array.copy currentKey
decrypt currentKey ciphertext plaintext
let mutable currentFitness = Fitness.get' plaintext fitness
let mutable bestFitness = currentFitness
let mutable keyCount = 0
while keyCount < keysLimit do
let swap1 = rand.Next (0, 26)
let swap2 = rand.Next (0, 25)
let swap2 = if swap2 < swap1 then swap2 else swap2 + 1
swap currentKey swap1 swap2
// We could do better here, because the plaintext changes
// only in well-defined ways from loop to loop.
// I'm lazy.
decrypt currentKey ciphertext plaintext
let newFitness = Fitness.get' plaintext fitness
if newFitness > bestFitness then
bestFitness <- newFitness
currentFitness <- newFitness
Array.blit currentKey 0 bestKey 0 currentKey.Length
elif newFitness > currentFitness - perturbations.[rand.Next (0, perturbations.Length)] then
// This guess wasn't the best we've had, but it's good enough when we
// add in a bit of jitter to get us out of local optima.
currentFitness <- newFitness
else
// Revert this guess, it wasn't sufficiently high fitness
swap currentKey swap1 swap2
keyCount <- keyCount + 1
decrypt bestKey ciphertext plaintext
let plaintext = backToString plaintext
bestKey, plaintext, bestFitness

70333
CipherSuite/tetragraphs.txt Normal file

File diff suppressed because it is too large Load Diff

14
Directory.Build.props Normal file
View File

@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<DebugType Condition=" '$(DebugType)' == '' ">embedded</DebugType>
<Deterministic>true</Deterministic>
<NetCoreTargetingPackRoot>[UNDEFINED]</NetCoreTargetingPackRoot>
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com" />
</ItemGroup>
</Project>

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Patrick Stevens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# dotnet-classical-ciphers
This is a partial .NET port of some *ancient* Pascal code I wrote with friends at school, for the primary purpose of solving the [National Cipher Challenge](https://www.cipherchallenge.org) challenges.
## Building from source
Just clone and `dotnet build`.
To upgrade the NuGet dependencies in the flake, run `nix build .#fetchDeps` and copy the resulting file into `nix/deps.nix`.

42
flake.lock generated Normal file
View File

@@ -0,0 +1,42 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1671548329,
"narHash": "sha256-OrC6R6zihRjFqdKFF3/vD3bkz44poONSII8ncre1Wh0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ba6ba2b90096dc49f448aa4d4d783b5081b1cc87",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixpkgs-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

90
flake.nix Normal file
View File

@@ -0,0 +1,90 @@
{
description = "Transcription of some classical cipher solvers from Pascal";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
};
outputs = {
self,
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
projectFile = "./CipherSuite/CipherSuite.fsproj";
testProjectFile = "./CipherSuite.Test/CipherSuite.Test.fsproj";
pname = "dotnet-cipher-suite";
dotnet-sdk = pkgs.dotnet-sdk_7;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_7_0;
version = "0.1";
dotnetTool = toolName: toolVersion: sha256:
pkgs.stdenvNoCC.mkDerivation rec {
name = toolName;
version = toolVersion;
nativeBuildInputs = [pkgs.makeWrapper];
src = pkgs.fetchNuGet {
pname = name;
version = version;
sha256 = sha256;
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
};
installPhase = ''
runHook preInstall
mkdir -p "$out/lib"
cp -r ./bin/* "$out/lib"
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${name}.dll"
runHook postInstall
'';
};
in {
packages = {
fantomas = dotnetTool "fantomas" "5.2.0-alpha-008" "sha256-1egphbWXTjs2I5aFaWibFDKgu3llP1o32o1X5vab6v4=";
fetchDeps = let
flags = [];
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
in
pkgs.writeShellScriptBin "fetch-${pname}-deps" (builtins.readFile (pkgs.substituteAll {
src = ./nix/fetchDeps.sh;
pname = pname;
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
projectFiles = toString (pkgs.lib.toList projectFile);
testProjectFiles = toString (pkgs.lib.toList testProjectFile);
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
packages = dotnet-sdk.packages;
storeSrc = pkgs.srcOnly {
src = ./.;
pname = pname;
version = version;
};
}));
default = pkgs.buildDotnetModule {
pname = pname;
name = "cipher-suite";
version = version;
src = ./.;
projectFile = projectFile;
nugetDeps = ./nix/deps.nix;
doCheck = true;
dotnet-sdk = dotnet-sdk;
dotnet-runtime = dotnet-runtime;
};
};
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
(with dotnetCorePackages;
combinePackages [
dotnet-sdk_7
dotnetPackages.Nuget
])
];
packages = [
pkgs.alejandra
pkgs.nodePackages.markdown-link-check
pkgs.shellcheck
];
};
});
}

6
hooks/pre-push Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
if ! dotnet tool run fantomas --check -r . ; then
echo "Formatting incomplete. Consider running 'dotnet tool run fantomas -r .'"
exit 1
fi

99
nix/deps.nix Normal file
View File

@@ -0,0 +1,99 @@
# This file was automatically generated by passthru.fetch-deps.
# Please don't edit it manually, your changes might get overwritten!
{fetchNuGet}: [
(fetchNuGet {
pname = "coverlet.collector";
version = "3.2.0";
sha256 = "1qxpv8v10p5wn162lzdm193gdl6c5f81zadj8h889dprlnj3g8yr";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "7.0.0";
sha256 = "1pgk3qk9p1s53wvja17744x4bf7zs3a3wf0dmxi66w1w06z7i85x";
})
(fetchNuGet {
pname = "FsUnit";
version = "5.2.0";
sha256 = "0l4n453slnynp8x372618yx6gjwywfai1dyl4m5iyw87d0iam7q3";
})
(fetchNuGet {
pname = "Microsoft.Build.Tasks.Git";
version = "1.1.1";
sha256 = "1bb5p4zlnfn88skkvymxfsn0jybqncl4356hwnic9jxdq2d4fz1w";
})
(fetchNuGet {
pname = "Microsoft.CodeCoverage";
version = "17.4.1";
sha256 = "0bf68gq6mc6kzri4zi8ydc0xrazqwqg38bhbpjpj90zmqc28kari";
})
(fetchNuGet {
pname = "Microsoft.NET.Test.Sdk";
version = "17.4.1";
sha256 = "02p1j9fncd4fb2hyp51kw49d0dz30vvazhzk24c9f5ccc00ijpra";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "1.1.0";
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
})
(fetchNuGet {
pname = "Microsoft.SourceLink.Common";
version = "1.1.1";
sha256 = "0xkdqs7az2cprar7jzjlgjpd64l6f8ixcmwmpkdm03fyb4s5m0bg";
})
(fetchNuGet {
pname = "Microsoft.SourceLink.GitHub";
version = "1.1.1";
sha256 = "099y35f2npvva3jk1zp8hn0vb9pwm2l0ivjasdly6y2idv53s5yy";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.ObjectModel";
version = "17.4.1";
sha256 = "0s68wf9yphm4hni9p6kwfk0mjld85f4hkrs93qbk5lzf6vv3kba1";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.TestHost";
version = "17.4.1";
sha256 = "1n9ilq8n5rhyxcri06njkxb0h2818dbmzddwd2rrvav91647m2s4";
})
(fetchNuGet {
pname = "Nerdbank.GitVersioning";
version = "3.5.119";
sha256 = "0g7a812lfa5y140pinpy5q3nl3xcarydflpxzzcyxxk4hjjlawy0";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.0";
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
})
(fetchNuGet {
pname = "Newtonsoft.Json";
version = "13.0.1";
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb";
})
(fetchNuGet {
pname = "NuGet.Frameworks";
version = "5.11.0";
sha256 = "0wv26gq39hfqw9md32amr5771s73f5zn1z9vs4y77cgynxr73s4z";
})
(fetchNuGet {
pname = "NUnit";
version = "3.13.3";
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
})
(fetchNuGet {
pname = "NUnit.Analyzers";
version = "3.5.0";
sha256 = "1dssqrzbqpx6xf517i4vya4m75pa0d52aach16n2im8vjp12i1j2";
})
(fetchNuGet {
pname = "NUnit3TestAdapter";
version = "4.3.1";
sha256 = "1j80cfrg0fflgw7wxi76mxj1wllwzcg4ck957knmjpghw5cw7lvv";
})
(fetchNuGet {
pname = "System.Reflection.Metadata";
version = "1.6.0";
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4";
})
]

73
nix/fetchDeps.sh Normal file
View File

@@ -0,0 +1,73 @@
#!/bin/bash
# This file was adapted from
# https://github.com/NixOS/nixpkgs/blob/b981d811453ab84fb3ea593a9b33b960f1ab9147/pkgs/build-support/dotnet/build-dotnet-module/default.nix#L173
set -euo pipefail
export PATH="@binPath@"
for arg in "$@"; do
case "$arg" in
--keep-sources|-k)
keepSources=1
shift
;;
--help|-h)
echo "usage: $0 [--keep-sources] [--help] <output path>"
echo " <output path> The path to write the lockfile to. A temporary file is used if this is not set"
echo " --keep-sources Don't remove temporary directories upon exit, useful for debugging"
echo " --help Show this help message"
exit
;;
esac
done
tmp=$(mktemp -td "@pname@-tmp-XXXXXX")
export tmp
HOME=$tmp/home
exitTrap() {
test -n "${ranTrap-}" && return
ranTrap=1
if test -n "${keepSources-}"; then
echo -e "Path to the source: $tmp/src\nPath to the fake home: $tmp/home"
else
rm -rf "$tmp"
fi
# Since mktemp is used this will be empty if the script didnt succesfully complete
if ! test -s "$depsFile"; then
rm -rf "$depsFile"
fi
}
trap exitTrap EXIT INT TERM
dotnetRestore() {
local -r project="${1-}"
local -r rid="$2"
dotnet restore "${project-}" \
-p:ContinuousIntegrationBuild=true \
-p:Deterministic=true \
--packages "$tmp/nuget_pkgs" \
--runtime "$rid" \
--no-cache \
--force
}
declare -a projectFiles=( @projectFiles@ )
declare -a testProjectFiles=( @testProjectFiles@ )
export DOTNET_NOLOGO=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
depsFile=$(realpath "${1:-$(mktemp -t "@pname@-deps-XXXXXX.nix")}")
mkdir -p "$tmp/nuget_pkgs"
storeSrc="@storeSrc@"
src="$tmp/src"
cp -rT "$storeSrc" "$src"
chmod -R +w "$src"
cd "$src"
echo "Restoring project..."
rids=("@rids@")
for rid in "${rids[@]}"; do
(( ${#projectFiles[@]} == 0 )) && dotnetRestore "" "$rid"
for project in "${projectFiles[@]-}" "${testProjectFiles[@]-}"; do
dotnetRestore "$project" "$rid"
done
done
echo "Successfully restored project"
echo "Writing lockfile..."
echo -e "# This file was automatically generated by passthru.fetch-deps.\n# Please don't edit it manually, your changes might get overwritten!\n" > "$depsFile"
nuget-to-nix "$tmp/nuget_pkgs" "@packages@" >> "$depsFile"
echo "Successfully wrote lockfile to $depsFile"