Skip to content

Commit

Permalink
Merge branch 'main' into use_circl_sign_interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
david415 committed Feb 22, 2024
2 parents 52c8b18 + d93a784 commit d490a88
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 255 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go

name: Go

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
89 changes: 79 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,91 @@


# HPQC

## hybrid post quantum cryptography
HPQC is known as hpqc.


[![Go Reference](https://pkg.go.dev/badge/github.com/katzenpost/hpqc.svg)](https://pkg.go.dev/github.com/katzenpost/hpqc)
[![Release](https://img.shields.io/github/v/tag/katzenpost/hpqc)](https://github.com/katzenpost/hpqc/tags)
[![Go Report Card](https://goreportcard.com/badge/github.com/katzenpost/hpqc)](https://goreportcard.com/report/github.com/katzenpost/hpqc)
[![CI](https://github.com/katzenpost/hpqc/actions/workflows/go.yml/badge.svg)](https://github.com/katzenpost/hpqc/actions/workflows/go.yml)


we have but two simple goals at the moment:

1. silo ALL of the katzenpost cryptography into this one library so that it's easier to audit,
easier to reason about. This will help us standardize our approach to solving cryptographic problems
across multiple protocols.
2. Provide a very niche cryptography library that other golang software projects can use if they
want hybrid constructions consisting of classical and post quantum cryptographic primitives.
## hybrid post quantum cryptography

Hybrid cryptographic constructions rely on a classical public key
primitive and a post quantum public key cryptographic primitive, namely:

* hybrid KEMs
* hybrid NIKEs
* hybrid signature schemes

This entire cryptography library is rendered in serviced to the
above post quantum trifecta of cryptographic primitives.
However, our main contributions are the following:

1. a set of generic NIKE interfaces for NIKE scheme, public key and private key types
2. generic hybrid NIKE, combines any two NIKEs into one
3. secure KEM combiner that can combine an arbtrary number of KEMs into one KEM
4. a "NIKE to KEM adapter" which uses an ad hoc hashed elgamal construction
5. cgo bindings for the Sphincs+ C reference source
6. cgo bindings for the CTIDH C source
7. generic hybrid signature scheme, combines any two signature schemes into one

All that having been said, we get our cryptographic primitives mostly from other cryptography
projects such as circl, highctidh, katzenpost, various golang cryptography libraries on github etc.

If you want a well known hybrid KEM that has a paper about it then maybe
Xwing is the KEM you are looking for. Otherwise you can construct your own
using our secure KEM combiner and or NIKE to KEM adapter.

Our secure KEM combiner is based on the Split PRF KEM combiner from this paper:

`Secure KEM Combiner` https://eprint.iacr.org/2018/024.pdf


| NIKE: Non-Interactive Key Exchange |
|:---:|
* X25519
* CTIDH511, CTIDH512, CTIDH1024, CTIDH2048
* X25519_CTIDH511, X25519_CTIDH512, X25519_CTIDH1024, X25519_CTIDH2048
* NOBS_CSIDH-512
* X25519_NOBS_CSIDH-512

| KEM: Key Encapsulation Methods |
|:---:|
* X25519 (adapted via ad hoc hashed elgamal construction)
* CTIDH1024 (adapted via ad hoc hashed elgamal construction)
* MLKEM-768
* Xwing
* McEliece
* NTRUPrime
* Kyber
* FrodoKEM

| SIGN: Cryptographic Signature Schemes |
|:---:|
* ed25519
* sphincs+
* ed25519_sphincs+
* ed25519_dilithium2/3


# licensing

this is agpl-3 licensed code however some modules written by other authors
is included here and in those cases we've included their LICENSE file in the
directory or in the top comment of the file.
hpqc is free libre open source software (FLOSS) under the AGPL-3.0 software license.
This git repository provides a LICENSE file, here: https://github.com/katzenpost/hpqc/blob/main/LICENSE


Read about free software philosophy --> https://www.gnu.org/philosophy/free-sw.html


* There are precisely two files which were borrowed
from cloudflare's `circl` cryptography library
which provide the kem and signature interfaces:

1. https://github.com/katzenpost/hpqc/blob/main/kem/interfaces.go
2. https://github.com/katzenpost/hpqc/blob/main/sign/interfaces.go

Those two files have their licenses attached at the top in a code comment.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ go 1.20
require (
codeberg.org/vula/highctidh v1.0.2024012400
filippo.io/edwards25519 v1.0.0
filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1
github.com/go-faster/xor v1.0.0
github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73
github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556
github.com/katzenpost/circl v1.3.9-0.20240217041329-ca5040c423ad
github.com/katzenpost/mlkem768 v0.0.1
github.com/katzenpost/circl v1.3.8-0.20240208050109-5fb03bb61607
github.com/katzenpost/mlkem768 v0.0.2
github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8
github.com/katzenpost/sphincsplus v0.0.2-0.20240114192234-1dc77b544e31
github.com/stretchr/testify v1.8.4
Expand Down
14 changes: 6 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ codeberg.org/vula/highctidh v1.0.2024012400 h1:W0vCA6HbtfONz3nQcfjFdrrO3dcFJTjYk
codeberg.org/vula/highctidh v1.0.2024012400/go.mod h1:admvznk7GhsrDih/BMy7jbIv9Y86U9vj7S5FVoquF/g=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1 h1:xbdqh5aDZeO0XqW896qVjKnAqRji9nkIwmsBEEbCA10=
filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1/go.mod h1:mIEHrcJ2xBlJRQwnRO0ujmZ+Rt6m6eNeCPq8E3Wkths=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand All @@ -12,14 +14,10 @@ github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73 h1:d3rq/Tz+RJ5h1xk
github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73/go.mod h1:ptK2MJqVLVEa/V/oK8n+MEyUDCSjSylW+jeNmCG1DJo=
github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556 h1:9gHByAWH1LydGefFGorN1ZBRZ/Oz9iozdzMvRTWpyRw=
github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556/go.mod h1:d9kxwmGOcutgP6bQwr2xaLInaW5yJsxsoPRyUIG0J/E=
github.com/katzenpost/circl v1.3.8 h1:F7oricmI2/fzblMH1dLcI+EXtW6fYgfNlMk2zQqCv8w=
github.com/katzenpost/circl v1.3.8/go.mod h1:Uv5vNSenUKpvNK2zT7llPz9RAM0NC5KiqdkCjHOdh8U=
github.com/katzenpost/circl v1.3.9-0.20240217000451-a5600f189729 h1:RBU+B402OHXKn0y9YsVcc2+QTk+h093HinB1aBkhGIk=
github.com/katzenpost/circl v1.3.9-0.20240217000451-a5600f189729/go.mod h1:+EBrwiGYs9S+qZqaqxujN1CReTNCMAG6p+31KkEDeeA=
github.com/katzenpost/circl v1.3.9-0.20240217041329-ca5040c423ad h1:cbCxk0AcVM0bZDdgcqKoJ9gd9Q3ju6BhyRDQVwoyDJc=
github.com/katzenpost/circl v1.3.9-0.20240217041329-ca5040c423ad/go.mod h1:+EBrwiGYs9S+qZqaqxujN1CReTNCMAG6p+31KkEDeeA=
github.com/katzenpost/mlkem768 v0.0.1 h1:5JqjyhLUAHhdtuBiOE7SEs9ZE7bxW+10mUwOP02hRkI=
github.com/katzenpost/mlkem768 v0.0.1/go.mod h1:EXp1RSzk/CWdWk+LKhMp51rxDB8IEZbMyze8TsIE79w=
github.com/katzenpost/circl v1.3.8-0.20240208050109-5fb03bb61607 h1:y5b/0ChgEj8HuAzoRwgHA9gF+5BMaRuiGjGy0kFYcIk=
github.com/katzenpost/circl v1.3.8-0.20240208050109-5fb03bb61607/go.mod h1:Uv5vNSenUKpvNK2zT7llPz9RAM0NC5KiqdkCjHOdh8U=
github.com/katzenpost/mlkem768 v0.0.2 h1:d8EQLqUXHYAJDEZZ5gRO9JxX8eTgF0292AIwNpnK5xc=
github.com/katzenpost/mlkem768 v0.0.2/go.mod h1:EXp1RSzk/CWdWk+LKhMp51rxDB8IEZbMyze8TsIE79w=
github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8 h1:TsKxH0x2RUwf5rBw67k15bqVM3oVbexA9oaTZQLIy3Y=
github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8/go.mod h1:Hmcrwom7jcEmGdo0CsyuJNnldPeyS+M07FuCbo7I8fw=
github.com/katzenpost/sphincsplus v0.0.2-0.20240114192234-1dc77b544e31 h1:fKGa/too1Br31gmoYmV2kE61gydj47Ed5K/g/CE+3Bs=
Expand Down
43 changes: 18 additions & 25 deletions kem/adapter/kem.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import (
"github.com/katzenpost/hpqc/kem"
"github.com/katzenpost/hpqc/kem/pem"
"github.com/katzenpost/hpqc/nike"
"github.com/katzenpost/hpqc/rand"
)

const (
// SeedSize is the number of bytes needed to seed deterministic methods below.
SeedSize = 32
)

var _ kem.PrivateKey = (*PrivateKey)(nil)
var _ kem.PublicKey = (*PublicKey)(nil)
var _ kem.Scheme = (*Scheme)(nil)

// PublicKey is an adapter for nike.PublicKey to kem.PublicKey.
type PublicKey struct {
publicKey nike.PublicKey
Expand Down Expand Up @@ -117,12 +120,20 @@ func (a *Scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
// Encapsulate generates a shared key ss for the public key and
// encapsulates it into a ciphertext ct.
func (a *Scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
seed := make([]byte, a.EncapsulationSeedSize())
_, err = rand.Reader.Read(seed)
theirPubkey, ok := pk.(*PublicKey)
if !ok || theirPubkey.scheme != a {
return nil, nil, kem.ErrTypeMismatch
}
myPubkey, sk2, err := a.GenerateKeyPair()
if err != nil {
return
return nil, nil, err
}
return a.EncapsulateDeterministically(pk, seed)
// ss = DH(my_privkey, their_pubkey)
ss = a.nike.DeriveSecret(sk2.(*PrivateKey).privateKey, theirPubkey.publicKey)
// ss2 = H(ss || their_pubkey || my_pubkey)
ss2 := a.hash(ss, theirPubkey.publicKey.Bytes(), myPubkey.(*PublicKey).publicKey.Bytes())
ct, _ = myPubkey.MarshalBinary()
return ct, ss2, nil
}

func (a *Scheme) hash(ss []byte, pubkey1 []byte, pubkey2 []byte) []byte {
Expand Down Expand Up @@ -268,24 +279,6 @@ func (a *Scheme) SeedSize() int {
// Implements ENCAPSULATE as described in NIKE to KEM adapter,
// see docs/specs/kemsphinx.rst
func (a *Scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (
[]byte, []byte, error) {
if len(seed) != a.EncapsulationSeedSize() {
return nil, nil, kem.ErrSeedSize
}
theirPubkey, ok := pk.(*PublicKey)
if !ok || theirPubkey.scheme != a {
return nil, nil, kem.ErrTypeMismatch
}
myPubkey, sk2 := a.DeriveKeyPair(seed)
// ss = DH(my_privkey, their_pubkey)
ss := a.nike.DeriveSecret(sk2.(*PrivateKey).privateKey, theirPubkey.publicKey)
// ss2 = H(ss || their_pubkey || my_pubkey)
ss2 := a.hash(ss, theirPubkey.publicKey.Bytes(), myPubkey.(*PublicKey).publicKey.Bytes())
ct, _ := myPubkey.MarshalBinary()
return ct, ss2, nil
}

// Size of seed used in EncapsulateDeterministically().
func (a *Scheme) EncapsulationSeedSize() int {
return SeedSize
ct, ss []byte, err error) {
panic("not implemented")
}
47 changes: 11 additions & 36 deletions kem/combiner/combiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package combiner

import (
"crypto/rand"
"errors"
"fmt"

Expand All @@ -20,6 +19,10 @@ var (
ErrUninitialized = errors.New("public or private key not initialized")
)

var _ kem.PrivateKey = (*PrivateKey)(nil)
var _ kem.PublicKey = (*PublicKey)(nil)
var _ kem.Scheme = (*Scheme)(nil)

// Public key of a combined KEMs.
type PublicKey struct {
scheme *Scheme
Expand Down Expand Up @@ -203,15 +206,6 @@ func (sch *Scheme) CiphertextSize() int {
return sum
}

// EncapsulationSeedSize returns the KEM's encapsulation seed size in bytes.
func (sch *Scheme) EncapsulationSeedSize() int {
sum := 0
for _, s := range sch.schemes {
sum += s.EncapsulationSeedSize()
}
return sum
}

// GenerateKeyPair generates a keypair.
func (sch *Scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
pubKeys := make([]kem.PublicKey, len(sch.schemes))
Expand Down Expand Up @@ -264,31 +258,7 @@ func (sch *Scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {

// Encapsulate creates a shared secret and ciphertext given a public key.
func (sch *Scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
seed := make([]byte, sch.EncapsulationSeedSize())
_, err = rand.Reader.Read(seed)
if err != nil {
return
}
return sch.EncapsulateDeterministically(pk, seed)
}

// EncapsulateDeterministically deterministircally encapsulates a share secret to the given public key and the given seed value.
func (sch *Scheme) EncapsulateDeterministically(publicKey kem.PublicKey, seed []byte) (ct, ss []byte, err error) {
if len(seed) != sch.EncapsulationSeedSize() {
return nil, nil, kem.ErrSeedSize
}

seeds := make([][]byte, len(sch.schemes))
offset := sch.schemes[0].EncapsulationSeedSize()
seeds[0] = seed[:offset]

for i := 1; i < len(sch.schemes); i++ {
seedSize := sch.schemes[i].EncapsulationSeedSize()
seeds[i] = seed[offset : offset+seedSize]
offset += seedSize
}

pub, ok := publicKey.(*PublicKey)
pub, ok := pk.(*PublicKey)
if !ok {
return nil, nil, kem.ErrTypeMismatch
}
Expand All @@ -298,7 +268,7 @@ func (sch *Scheme) EncapsulateDeterministically(publicKey kem.PublicKey, seed []
ciphertextBlob := []byte{}

for i := 0; i < len(sch.schemes); i++ {
cct, ss, err := sch.schemes[i].EncapsulateDeterministically(pub.keys[i], seeds[i])
cct, ss, err := sch.schemes[i].Encapsulate(pub.keys[i])
if err != nil {
return nil, nil, err
}
Expand All @@ -312,6 +282,11 @@ func (sch *Scheme) EncapsulateDeterministically(publicKey kem.PublicKey, seed []
return ciphertextBlob, ss, nil
}

// EncapsulateDeterministically deterministircally encapsulates a share secret to the given public key and the given seed value.
func (sch *Scheme) EncapsulateDeterministically(publicKey kem.PublicKey, seed []byte) (ct, ss []byte, err error) {
panic("not implemented")
}

// Decapsulate decrypts a given KEM ciphertext using the given private key.
func (sch *Scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
if len(ct) != sch.CiphertextSize() {
Expand Down
Loading

0 comments on commit d490a88

Please sign in to comment.