diff --git a/packaging/dotnet-core/.gitignore b/packaging/dotnet-core/.gitignore
new file mode 100644
index 00000000..bf99e1ea
--- /dev/null
+++ b/packaging/dotnet-core/.gitignore
@@ -0,0 +1,5 @@
+build
+cache
+libsodium.*.nupkg
+libsodium.*.nuspec
+Makefile
diff --git a/packaging/dotnet-core/README.md b/packaging/dotnet-core/README.md
new file mode 100644
index 00000000..370afdc4
--- /dev/null
+++ b/packaging/dotnet-core/README.md
@@ -0,0 +1,48 @@
+This directory contains scripts and files to package libsodium for .NET Core.
+
+In .NET Core, it is customary to provide pre-compiled binaries for all platforms
+as NuGet packages. The purpose of the `prepare.py` script in this directory is
+to generate a `Makefile` that downloads pre-compiled libsodium binaries from a
+list of sources and creates a NuGet package that can be uploaded to
+[nuget.org](https://nuget.org/).
+
+
+#### Sources
+
+The list of sources is located in `prepare.py` and needs to be updated on each
+libsodium release. Currently, libsodium binaries are obtained in the following
+ways:
+
+* For Windows, the binaries are taken from
+ [download.libsodium.org](https://download.libsodium.org/libsodium/releases/).
+* For macOS, the binaries are extracted from the
+ [Homebrew libsodium bottle](https://bintray.com/homebrew/bottles/libsodium).
+* For Linux, libsodium is compiled on and downloaded from the
+ [openSUSE Build Service](https://build.opensuse.org/package/show/home:nsec/libsodium).
+
+
+#### Metadata
+
+The metadata for the NuGet package is located in `libsodium.nuspec`. On each
+invocation, the `prepare.py` script generates a new version number and creates a
+`.nuspec` file that contains the metadata from `libsodium.nuspec`, the generated
+version number and the list of binaries.
+
+
+#### Making a release
+
+1. Update `_service` to reflect the current libsodium version.
+2. Download `libsodium.spec` and `libsodium.changes` from
+ [here](https://build.opensuse.org/package/show?project=devel%3Alibraries%3Ac_c%2B%2B&package=libsodium).
+3. Upload `_service`, `libsodium.spec`, `libsodium.changes` and the current
+ `libsodium-{version}.tar.gz` to openSUSE Build Service.
+4. Update the libsodium version, file names and download URLs in `prepare.py`.
+5. Run `./prepare.py` to generate the `Makefile` and
+ `libsodium.{version}.nuspec`.
+6. Run `make` to download the binaries and create `libsodium.{version}.nupkg`.
+ You may need to install `unzip`, `rpm2cpio` and `dotnet-dev` first.
+7. Verify that everything in the `.nupkg` file is in place.
+8. Publish the release by uploading the `.nupkg` file to
+ [nuget.org](https://nuget.org/).
+9. Commit the updated `prepare.py` and `version.json` to the libsodium
+ repository.
diff --git a/packaging/dotnet-core/_service b/packaging/dotnet-core/_service
new file mode 100644
index 00000000..c6895234
--- /dev/null
+++ b/packaging/dotnet-core/_service
@@ -0,0 +1,17 @@
+
+
+ ftp
+ ftp.us.debian.org
+ /debian/pool/main/libs/libsodium/libsodium_1.0.11-1.dsc
+
+
+ ftp
+ ftp.us.debian.org
+ /debian/pool/main/libs/libsodium/libsodium_1.0.11.orig.tar.gz
+
+
+ ftp
+ ftp.us.debian.org
+ /debian/pool/main/libs/libsodium/libsodium_1.0.11-1.debian.tar.xz
+
+
diff --git a/packaging/dotnet-core/libsodium.nuspec b/packaging/dotnet-core/libsodium.nuspec
new file mode 100644
index 00000000..12a254a9
--- /dev/null
+++ b/packaging/dotnet-core/libsodium.nuspec
@@ -0,0 +1,19 @@
+
+
+
+ libsodium
+
+ Frank Denis
+ Frank Denis
+ true
+ https://raw.githubusercontent.com/jedisct1/libsodium/master/LICENSE
+ https://libsodium.org/
+
+ A modern and easy-to-use crypto library, packaged for use with .NET Core
+ © 2013-2017 Frank Denis
+
+
+
+
+
+
diff --git a/packaging/dotnet-core/prepare.py b/packaging/dotnet-core/prepare.py
new file mode 100644
index 00000000..660304cc
--- /dev/null
+++ b/packaging/dotnet-core/prepare.py
@@ -0,0 +1,268 @@
+#!/usr/bin/python3
+
+import datetime
+import json
+import os.path
+import sys
+import xml.etree.ElementTree as ET
+
+#
+# OUTPUT CONFIGURATION
+#
+
+# Package metadata
+NUPKG_ID = 'libsodium'
+NUPKG_VERSION = '1.0.11'
+
+# The names of the libsodium binaries in the package
+LIBSODIUM_DLL = 'libsodium.dll'
+LIBSODIUM_DYLIB = 'libsodium.dylib'
+LIBSODIUM_SO = 'libsodium.so'
+
+#
+# INPUT CONFIGURATION
+#
+
+# The archives to download
+WIN_FILE = 'libsodium-1.0.11-msvc.zip'
+DEB_FILE = 'libsodium18_1.0.11-1_amd64.deb'
+RPM_FILE = 'libsodium18-1.0.11-14.1.x86_64.rpm'
+OSX_FILE = 'libsodium-1.0.11.{0}.bottle.tar.gz'
+
+# The URLs of the archives
+OFFICIAL_URL = 'https://download.libsodium.org/libsodium/releases/{0}'
+OPENSUSE_URL = 'http://download.opensuse.org/repositories/home:/nsec/{0}/{1}/{2}'
+HOMEBREW_URL = 'https://bintray.com/homebrew/bottles/download_file?file_path={0}'
+
+# The files within the archives to extract
+WIN_LIB = '{0}/Release/v140/dynamic/libsodium.dll'
+DEB_LIB = './usr/lib/x86_64-linux-gnu/libsodium.so.18.1.1'
+RPM_LIB = './usr/lib64/libsodium.so.18.1.1'
+OSX_LIB = 'libsodium/1.0.11/lib/libsodium.18.dylib'
+
+# Commands to extract a file from an archive
+DEB_EXTRACT = 'ar -p {0} data.tar.xz | tar xJ "{1}"'
+RPM_EXTRACT = 'rpm2cpio {0} | cpio -i "{1}"'
+TAR_EXTRACT = 'tar xzf {0} "{1}"'
+ZIP_EXTRACT = 'unzip {0} "{1}"'
+
+# The inputs
+INPUTS = [
+
+ ( 'win10-x64',
+ WIN_FILE,
+ OFFICIAL_URL.format(WIN_FILE),
+ WIN_LIB.format('x64'),
+ ZIP_EXTRACT,
+ LIBSODIUM_DLL),
+
+ ( 'win10-x86',
+ WIN_FILE,
+ OFFICIAL_URL.format(WIN_FILE),
+ WIN_LIB.format('Win32'),
+ ZIP_EXTRACT,
+ LIBSODIUM_DLL),
+
+ ( 'debian.8-x64',
+ DEB_FILE,
+ OPENSUSE_URL.format('Debian_8.0', 'amd64', DEB_FILE),
+ DEB_LIB,
+ DEB_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'ubuntu.14.04-x64',
+ DEB_FILE,
+ OPENSUSE_URL.format('xUbuntu_14.04', 'amd64', DEB_FILE),
+ DEB_LIB,
+ DEB_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'ubuntu.16.04-x64',
+ DEB_FILE,
+ OPENSUSE_URL.format('xUbuntu_16.04', 'amd64', DEB_FILE),
+ DEB_LIB,
+ DEB_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'ubuntu.16.10-x64',
+ DEB_FILE,
+ OPENSUSE_URL.format('xUbuntu_16.10', 'amd64', DEB_FILE),
+ DEB_LIB,
+ DEB_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'centos.7-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('CentOS_7', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'fedora.23-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('Fedora_23', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'fedora.24-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('Fedora_24', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'fedora.25-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('Fedora_25', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'opensuse.42.1-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('openSUSE_Leap_42.1', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'opensuse.42.2-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('openSUSE_Leap_42.2', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'rhel.7-x64',
+ RPM_FILE,
+ OPENSUSE_URL.format('RHEL_7', 'x86_64', RPM_FILE),
+ RPM_LIB,
+ RPM_EXTRACT,
+ LIBSODIUM_SO),
+
+ ( 'osx.10.10-x64',
+ OSX_FILE.format('yosemite'),
+ HOMEBREW_URL.format(OSX_FILE.format('yosemite')),
+ OSX_LIB,
+ TAR_EXTRACT,
+ LIBSODIUM_DYLIB),
+
+ ( 'osx.10.11-x64',
+ OSX_FILE.format('el_capitan'),
+ HOMEBREW_URL.format(OSX_FILE.format('el_capitan')),
+ OSX_LIB,
+ TAR_EXTRACT,
+ LIBSODIUM_DYLIB),
+
+ ( 'osx.10.12-x64',
+ OSX_FILE.format('sierra'),
+ HOMEBREW_URL.format(OSX_FILE.format('sierra')),
+ OSX_LIB,
+ TAR_EXTRACT,
+ LIBSODIUM_DYLIB),
+
+]
+
+# The version cookie
+COOKIE_FILE = 'version.json'
+
+#
+# INTERMEDIATE FILES
+#
+
+CACHEDIR = 'cache'
+TEMPDIR = 'build'
+
+#
+# DO NOT EDIT BELOW THIS LINE
+#
+
+class Item:
+ def __init__(self, input, cachedir, tempdir):
+ rid, archive, url, file, extract, lib = input
+
+ self.rid = rid
+ self.archive = archive
+ self.url = url
+ self.file = file
+ self.extract = extract
+ self.lib = lib
+
+ self.cachefile = os.path.join(cachedir, rid, archive)
+ self.sourcedir = os.path.join(tempdir, rid)
+ self.sourcefile = os.path.join(tempdir, rid, os.path.normpath(file))
+ self.targetfile = os.path.join('runtimes', rid, 'native', lib)
+
+def create_nuspec(template, nuspec, version, items):
+ tree = ET.parse(template)
+ package = tree.getroot()
+ metadata = package.find('metadata')
+ metadata.find('version').text = version
+ files = package.find('files')
+ for item in items:
+ ET.SubElement(files, 'file', src=item.sourcefile, target=item.targetfile).tail = '\n'
+ tree.write(nuspec, 'ascii', '')
+
+def create_makefile(makefile, nupkg, nuspec, items):
+ with open(makefile, 'w') as f:
+ for item in items:
+ f.write('FILES += {0}\n'.format(item.sourcefile))
+ f.write('\n')
+ f.write('{0}: {1} $(FILES)\n\tdotnet nuget pack $<\n'.format(nupkg, nuspec))
+ for item in items:
+ f.write('\n')
+ f.write('{0}:\n\t@mkdir -p $(dir $@)\n\tcurl -f#Lo $@ "{1}"\n'.format(item.cachefile, item.url))
+ for item in items:
+ f.write('\n')
+ f.write('{0}: {1}\n\t@mkdir -p $(dir $@)\n\tcd {2} && {3}\n'.format(
+ item.sourcefile,
+ item.cachefile,
+ item.sourcedir,
+ item.extract.format(os.path.relpath(item.cachefile, item.sourcedir), item.file)))
+
+def make_prerelease_version(version, suffix, cookie_file):
+ cookies = dict()
+ if os.path.isfile(cookie_file):
+ with open(cookie_file, 'r') as f:
+ cookies = json.load(f)
+ cookie = cookies.get(suffix, '---').split('-')
+ year, month, day, *rest = datetime.datetime.utcnow().timetuple()
+ major = '{0:03}{1:02}'.format(year * 12 + month - 23956, day)
+ minor = int(cookie[3]) + 1 if cookie[:3] == [version, suffix, major] else 1
+ result = '{0}-{1}-{2}-{3:02}'.format(version, suffix, major, minor)
+ cookies[suffix] = result
+ with open(cookie_file, 'w') as f:
+ json.dump(cookies, f, indent=4, sort_keys=True)
+ return result
+
+def main(args):
+ if len(args) > 2 or len(args) > 1 and not args[1].isalpha:
+ print('usage: {0} [label]'.format(os.path.basename(args[0])))
+ sys.exit(1)
+
+ version = NUPKG_VERSION
+
+ if len(args) > 1:
+ suffix = args[1].lower()
+ else:
+ suffix = 'preview'
+
+ if suffix != 'release':
+ version = make_prerelease_version(version, suffix, COOKIE_FILE)
+ print('updated', COOKIE_FILE)
+
+ template = NUPKG_ID + '.nuspec'
+ nuspec = NUPKG_ID + '.' + version + '.nuspec'
+ nupkg = NUPKG_ID + '.' + version + '.nupkg'
+
+ tempdir = os.path.join(TEMPDIR, version)
+ items = [Item(input, CACHEDIR, tempdir) for input in INPUTS]
+
+ create_nuspec(template, nuspec, version, items)
+ print('created', nuspec)
+
+ create_makefile('Makefile', nupkg, nuspec, items)
+ print('created', 'Makefile', 'to make', nupkg)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/packaging/dotnet-core/version.json b/packaging/dotnet-core/version.json
new file mode 100644
index 00000000..26166e89
--- /dev/null
+++ b/packaging/dotnet-core/version.json
@@ -0,0 +1,3 @@
+{
+ "preview": "1.0.11-preview-24920-01"
+}
\ No newline at end of file