├── nike ├── csidh │ ├── csidh_not_supported.go │ ├── csidh_test.go │ └── csidh.go ├── hybrid │ ├── no_nobs_csidh.go │ ├── nobs_csidh.go │ └── ctidh.go ├── diffiehellman │ ├── dh_test.go │ └── LICENSE ├── ctidh │ ├── ctidh1024 │ │ ├── ctidh_test.go │ │ └── ctidh.go │ ├── ctidh2048 │ │ ├── ctidh_test.go │ │ └── ctidh.go │ ├── ctidh511 │ │ ├── ctidh_test.go │ │ └── ctidh.go │ └── ctidh512 │ │ ├── ctidh_test.go │ │ └── ctidh.go ├── schemes │ ├── schemes.go │ └── schemes_test.go ├── nike.go ├── x25519 │ └── ecdh_test.go ├── pem │ └── pem.go └── x448 │ └── x448.go ├── sign ├── sphincsplus │ ├── sphincs_not_supported.go │ ├── sphincs_test.go │ └── sphincs.go ├── hybrid │ ├── sphincsplus_not_supported.go │ ├── sphincsplus.go │ └── hybrid.go ├── schemes │ ├── schemes.go │ └── schemes_test.go ├── pem │ └── pem.go ├── ed25519 │ └── eddsa_test.go └── interfaces.go ├── hash └── hash.go ├── .github └── workflows │ ├── go.yml │ ├── macos-golang-test.yml │ ├── ubuntu-golang-cross-compile-test.yml │ └── windows-msys-64bit-gcc-ucrt-msvcrt-golang-test.yml ├── kem ├── adapter │ └── kem_test.go ├── schemes │ ├── kem_benchmark_test.go │ ├── kem_test.go │ └── schemes.go ├── mkem │ ├── ciphertext.go │ ├── mkem_test.go │ └── mkem.go ├── sntrup │ └── sntrup_test.go ├── hybrid │ ├── LICENSE │ └── hybrid.go ├── util │ └── split_prf.go ├── mlkem768 │ └── mlkem768.go ├── pem │ └── pem.go ├── xwing │ └── xwing.go └── interfaces.go ├── rand ├── rand_fallback.go ├── deterministic_rand_test.go ├── rand_test.go ├── deterministic_rand_reader.go ├── rand_reader.go ├── rand_linux.go └── math.go ├── util ├── ctIsZero.go ├── explicitBzero.go └── pem │ └── pem.go ├── go.mod ├── examples ├── nike_and_cipher │ └── main.go └── kem_and_cipher │ └── main.go ├── misc └── install-debian-go-deps-by-arch.sh └── go.sum /nike/csidh/csidh_not_supported.go: -------------------------------------------------------------------------------- 1 | //go:build !amd64 && !arm64 2 | 3 | package csidh 4 | 5 | import "github.com/katzenpost/hpqc/nike" 6 | 7 | var NOBS_CSIDH512Scheme nike.Scheme = nil 8 | -------------------------------------------------------------------------------- /sign/sphincsplus/sphincs_not_supported.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package sphincsplus 4 | 5 | import "github.com/katzenpost/hpqc/sign" 6 | 7 | func Scheme() sign.Scheme { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /sign/hybrid/sphincsplus_not_supported.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | // SPDX-FileCopyrightText: (c) 2024 David Stainton 4 | // SPDX-License-Identifier: AGPL-3.0-only 5 | 6 | package hybrid 7 | 8 | import "github.com/katzenpost/hpqc/sign" 9 | 10 | var Ed25519Sphincs sign.Scheme = nil 11 | var Ed448Sphincs sign.Scheme = nil 12 | -------------------------------------------------------------------------------- /nike/hybrid/no_nobs_csidh.go: -------------------------------------------------------------------------------- 1 | //go:build ppc64le 2 | 3 | package hybrid 4 | 5 | import ( 6 | "github.com/katzenpost/hpqc/nike" 7 | ecdh "github.com/katzenpost/hpqc/nike/x25519" 8 | "github.com/katzenpost/hpqc/rand" 9 | ) 10 | 11 | var NOBS_CSIDH512X25519 nike.Scheme = &Scheme{ 12 | name: "NOBS_CSIDH-X25519", 13 | first: ecdh.Scheme(rand.Reader), 14 | second: nil, 15 | } 16 | -------------------------------------------------------------------------------- /nike/hybrid/nobs_csidh.go: -------------------------------------------------------------------------------- 1 | //go:build !ppc64le 2 | 3 | package hybrid 4 | 5 | import ( 6 | "github.com/katzenpost/hpqc/nike" 7 | "github.com/katzenpost/hpqc/nike/csidh" 8 | ecdh "github.com/katzenpost/hpqc/nike/x25519" 9 | "github.com/katzenpost/hpqc/rand" 10 | ) 11 | 12 | var NOBS_CSIDH512X25519 nike.Scheme = &Scheme{ 13 | name: "NOBS_CSIDH-X25519", 14 | first: ecdh.Scheme(rand.Reader), 15 | second: csidh.NOBS_CSIDH512Scheme, 16 | } 17 | -------------------------------------------------------------------------------- /hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "encoding" 5 | 6 | "golang.org/x/crypto/blake2b" 7 | ) 8 | 9 | const HashSize = blake2b.Size256 10 | 11 | func Sum256(data []byte) [blake2b.Size256]byte { 12 | return blake2b.Sum256(data) 13 | } 14 | 15 | func Sum256From(key encoding.BinaryMarshaler) [blake2b.Size256]byte { 16 | blob, err := key.MarshalBinary() 17 | if err != nil { 18 | panic(err) 19 | } 20 | return blake2b.Sum256(blob) 21 | } 22 | -------------------------------------------------------------------------------- /nike/diffiehellman/dh_test.go: -------------------------------------------------------------------------------- 1 | package diffiehellman 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func NoTestDHKeyLengths(t *testing.T) { 10 | s := Scheme() 11 | 12 | for i := 0; i < 1000; i++ { 13 | pubkey1, privkey1, err := s.GenerateKeyPair() 14 | require.NoError(t, err) 15 | 16 | require.Equal(t, s.PublicKeySize(), len(pubkey1.Bytes())) 17 | require.Equal(t, s.PrivateKeySize(), len(privkey1.Bytes())) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sign/hybrid/sphincsplus.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | // SPDX-FileCopyrightText: (c) 2024 David Stainton 5 | // SPDX-License-Identifier: AGPL-3.0-only 6 | 7 | package hybrid 8 | 9 | import ( 10 | "github.com/katzenpost/circl/sign/ed448" 11 | 12 | "github.com/katzenpost/hpqc/sign/ed25519" 13 | "github.com/katzenpost/hpqc/sign/sphincsplus" 14 | ) 15 | 16 | var Ed25519Sphincs = New("Ed25519 Sphincs+", ed25519.Scheme(), sphincsplus.Scheme()) 17 | var Ed448Sphincs = New("Ed448-Sphincs+", ed448.Scheme(), sphincsplus.Scheme()) 18 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go tests 2 | on: [push, pull_request] 3 | permissions: 4 | contents: read 5 | jobs: 6 | test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-latest] 11 | arch: [amd64] 12 | go-version: ["1.23.0"] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Install Go (from go.mod) 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version-file: go.mod 23 | check-latest: true 24 | - name: Run tests 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /nike/csidh/csidh_test.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 || arm64 2 | 3 | package csidh 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestCtidhNike(t *testing.T) { 12 | scheme := NOBS_CSIDH512Scheme 13 | 14 | alicePublicKey, alicePrivateKey, err := scheme.GenerateKeyPair() 15 | require.NoError(t, err) 16 | 17 | tmp := scheme.DerivePublicKey(alicePrivateKey.(*PrivateKey)) 18 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 19 | 20 | bobPubKey, bobPrivKey, err := scheme.GenerateKeyPair() 21 | require.NoError(t, err) 22 | 23 | aliceS := scheme.DeriveSecret(alicePrivateKey, bobPubKey) 24 | 25 | bobS := scheme.DeriveSecret(bobPrivKey, alicePublicKey.(*PublicKey)) 26 | require.Equal(t, bobS, aliceS) 27 | } 28 | -------------------------------------------------------------------------------- /kem/adapter/kem_test.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | ecdh "github.com/katzenpost/hpqc/nike/x25519" 9 | "github.com/katzenpost/hpqc/rand" 10 | ) 11 | 12 | // NOTE that there is a deterministic which covers the behavior this 13 | // NIKE to KEM adapter, located in core/crypto/kem/schemes/kem_test.go 14 | 15 | func TestNikeToKemAdapter(t *testing.T) { 16 | ecdhNike := ecdh.Scheme(rand.Reader) 17 | s := FromNIKE(ecdhNike) 18 | 19 | t.Logf("hello my name is %s", s.Name()) 20 | 21 | pubkey1, privkey1, err := s.GenerateKeyPair() 22 | require.NoError(t, err) 23 | 24 | ct, ssA, err := s.Encapsulate(pubkey1) 25 | require.NoError(t, err) 26 | 27 | ssB, err := s.Decapsulate(privkey1, ct) 28 | require.NoError(t, err) 29 | 30 | require.Equal(t, ssA, ssB) 31 | 32 | t.Logf("our shared key is %x", ssA) 33 | } 34 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh1024/ctidh_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh1024 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | ctidh "codeberg.org/vula/highctidh/src/ctidh1024" 12 | ) 13 | 14 | func TestCTIDH1024_NIKE(t *testing.T) { 15 | ctidhNike := Scheme() 16 | 17 | alicePublicKey, alicePrivateKey, err := ctidhNike.GenerateKeyPair() 18 | require.NoError(t, err) 19 | 20 | tmp := ctidh.DerivePublicKey(alicePrivateKey.(*PrivateKey).privateKey) 21 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 22 | 23 | bobPubKey, bobPrivKey, err := ctidhNike.GenerateKeyPair() 24 | require.NoError(t, err) 25 | 26 | aliceS := ctidhNike.DeriveSecret(alicePrivateKey, bobPubKey) 27 | 28 | bobS := ctidh.DeriveSecret(bobPrivKey.(*PrivateKey).privateKey, alicePublicKey.(*PublicKey).publicKey) 29 | require.Equal(t, bobS, aliceS) 30 | } 31 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh2048/ctidh_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh2048 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | ctidh "codeberg.org/vula/highctidh/src/ctidh2048" 12 | ) 13 | 14 | func TestCTIDH2048_NIKE(t *testing.T) { 15 | ctidhNike := Scheme() 16 | 17 | alicePublicKey, alicePrivateKey, err := ctidhNike.GenerateKeyPair() 18 | require.NoError(t, err) 19 | 20 | tmp := ctidh.DerivePublicKey(alicePrivateKey.(*PrivateKey).privateKey) 21 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 22 | 23 | bobPubKey, bobPrivKey, err := ctidhNike.GenerateKeyPair() 24 | require.NoError(t, err) 25 | 26 | aliceS := ctidhNike.DeriveSecret(alicePrivateKey, bobPubKey) 27 | 28 | bobS := ctidh.DeriveSecret(bobPrivKey.(*PrivateKey).privateKey, alicePublicKey.(*PublicKey).publicKey) 29 | require.Equal(t, bobS, aliceS) 30 | } 31 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh511/ctidh_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh511 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | ctidh "codeberg.org/vula/highctidh/src/ctidh511" 12 | ) 13 | 14 | func TestCTIDH511_NIKE(t *testing.T) { 15 | ctidhNike := Scheme() 16 | 17 | alicePublicKey, alicePrivateKey, err := ctidhNike.GenerateKeyPair() 18 | require.NoError(t, err) 19 | 20 | tmp := ctidh.DerivePublicKey(alicePrivateKey.(*PrivateKey).privateKey) 21 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 22 | 23 | bobPubKey, bobPrivKey, err := ctidhNike.GenerateKeyPair() 24 | require.NoError(t, err) 25 | 26 | aliceS := ctidhNike.DeriveSecret(alicePrivateKey, bobPubKey) 27 | 28 | bobS := ctidh.DeriveSecret(bobPrivKey.(*PrivateKey).privateKey, alicePublicKey.(*PublicKey).publicKey) 29 | require.Equal(t, bobS, aliceS) 30 | } 31 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh512/ctidh_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh512 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | ctidh "codeberg.org/vula/highctidh/src/ctidh512" 12 | ) 13 | 14 | func TestCTIDH512_NIKE(t *testing.T) { 15 | ctidhNike := Scheme() 16 | 17 | alicePublicKey, alicePrivateKey, err := ctidhNike.GenerateKeyPair() 18 | require.NoError(t, err) 19 | 20 | tmp := ctidh.DerivePublicKey(alicePrivateKey.(*PrivateKey).privateKey) 21 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 22 | 23 | bobPubKey, bobPrivKey, err := ctidhNike.GenerateKeyPair() 24 | require.NoError(t, err) 25 | 26 | aliceS := ctidhNike.DeriveSecret(alicePrivateKey, bobPubKey) 27 | 28 | bobS := ctidh.DeriveSecret(bobPrivKey.(*PrivateKey).privateKey, alicePublicKey.(*PublicKey).publicKey) 29 | require.Equal(t, bobS, aliceS) 30 | } 31 | -------------------------------------------------------------------------------- /rand/rand_fallback.go: -------------------------------------------------------------------------------- 1 | // rand_fallback.go - Portable fallback for non-Linux systems. 2 | // Copyright (C) 2016 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | // +build !linux 18 | 19 | package rand 20 | 21 | import "crypto/rand" 22 | 23 | func init() { 24 | Reader = rand.Reader 25 | initWhitening() 26 | } 27 | -------------------------------------------------------------------------------- /util/ctIsZero.go: -------------------------------------------------------------------------------- 1 | // ctIsZero.go - Constant time buffer is zero. 2 | // Copyright (C) 2017 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package util 18 | 19 | // CtIsZero returns true iff the buffer b is all 0x00, doing the check in 20 | // constant time. 21 | func CtIsZero(b []byte) bool { 22 | var sum byte 23 | for _, v := range b { 24 | sum |= v 25 | } 26 | return sum == 0 27 | } 28 | -------------------------------------------------------------------------------- /util/explicitBzero.go: -------------------------------------------------------------------------------- 1 | // explicitBzero.go - Explicitly clear a memory buffer. 2 | // Copyright (C) 2017 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package util 18 | 19 | import "runtime" 20 | 21 | // ExplicitBzero explicitly clears out the buffer b, by filling it with 0x00 22 | // bytes. 23 | // 24 | //go:noinline 25 | func ExplicitBzero(b []byte) { 26 | for i := range b { 27 | b[i] = 0 28 | } 29 | runtime.KeepAlive(b) 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/macos-golang-test.yml: -------------------------------------------------------------------------------- 1 | name: MacOS Golang build and test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.OS }} 8 | strategy: 9 | matrix: 10 | OS: ["macos-14"] 11 | go-version: ["1.23.0"] 12 | fail-fast: false 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Setup Go ${{ matrix.go-version }} 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: ${{ matrix.go-version }} 21 | 22 | - name: Display Go version 23 | shell: bash 24 | run: go version 25 | 26 | - name: Install golang dependencies 27 | shell: bash 28 | run: | 29 | export HIGHCTIDH_PORTABLE=1 30 | export CGO_ENABLED=1 31 | go get -v ./... 32 | 33 | - name: Build golang 34 | shell: bash 35 | run: | 36 | export HIGHCTIDH_PORTABLE=1 37 | export CGO_ENABLED=1 38 | go build -v ./... 39 | 40 | - name: Golang test 41 | env: 42 | CGO_LDFLAGS: -Wl,-stack_size,0x1F40000 43 | shell: bash 44 | run: | 45 | export HIGHCTIDH_PORTABLE=1 46 | export CGO_ENABLED=1 47 | go test -v ./... 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/katzenpost/hpqc 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | codeberg.org/vula/highctidh v1.0.2024092800 7 | filippo.io/edwards25519 v1.0.0 8 | filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1 9 | github.com/agl/gcmsiv v0.0.0-20190418185415-e8dcd2f151dc 10 | github.com/fxamacker/cbor/v2 v2.7.0 11 | github.com/go-faster/xor v1.0.0 12 | github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73 13 | github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556 14 | github.com/katzenpost/chacha20poly1305 v0.0.0-20211026103954-7b6fb2fc0129 15 | github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c 16 | github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8 17 | github.com/katzenpost/sphincsplus v0.0.2 18 | github.com/stretchr/testify v1.8.4 19 | gitlab.com/elixxir/crypto v0.0.9 20 | gitlab.com/xx_network/crypto v0.0.6 21 | golang.org/x/crypto v0.36.0 22 | ) 23 | 24 | require ( 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/mattn/go-pointer v0.0.1 // indirect 27 | github.com/pkg/errors v0.9.1 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 30 | github.com/x448/float16 v0.8.4 // indirect 31 | golang.org/x/sys v0.31.0 // indirect 32 | gopkg.in/yaml.v3 v3.0.1 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /nike/diffiehellman/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, xx network SEZC 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation and/or 10 | other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /nike/hybrid/ctidh.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package hybrid 5 | 6 | import ( 7 | "github.com/katzenpost/hpqc/nike" 8 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh1024" 9 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh2048" 10 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh511" 11 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh512" 12 | "github.com/katzenpost/hpqc/nike/x25519" 13 | "github.com/katzenpost/hpqc/nike/x448" 14 | "github.com/katzenpost/hpqc/rand" 15 | ) 16 | 17 | var CTIDH511X25519 nike.Scheme = &Scheme{ 18 | name: "CTIDH511-X25519", 19 | first: ctidh511.Scheme(), 20 | second: x25519.Scheme(rand.Reader), 21 | } 22 | 23 | var CTIDH512X25519 nike.Scheme = &Scheme{ 24 | name: "CTIDH512-X25519", 25 | first: ctidh512.Scheme(), 26 | second: x25519.Scheme(rand.Reader), 27 | } 28 | 29 | var CTIDH512X448 nike.Scheme = &Scheme{ 30 | name: "CTIDH512-X448", 31 | second: ctidh512.Scheme(), 32 | first: x448.Scheme(rand.Reader), 33 | } 34 | 35 | var CTIDH1024X25519 nike.Scheme = &Scheme{ 36 | name: "CTIDH1024-X25519", 37 | second: ctidh1024.Scheme(), 38 | first: x25519.Scheme(rand.Reader), 39 | } 40 | 41 | var CTIDH1024X448 nike.Scheme = &Scheme{ 42 | name: "CTIDH1024-X448", 43 | first: ctidh1024.Scheme(), 44 | second: x448.Scheme(rand.Reader), 45 | } 46 | 47 | var CTIDH2048X448 nike.Scheme = &Scheme{ 48 | name: "CTIDH2048-X448", 49 | first: ctidh2048.Scheme(), 50 | second: x448.Scheme(rand.Reader), 51 | } 52 | -------------------------------------------------------------------------------- /sign/schemes/schemes.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package schemes 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/katzenpost/circl/sign/ed448" 10 | "github.com/katzenpost/circl/sign/eddilithium2" 11 | "github.com/katzenpost/circl/sign/eddilithium3" 12 | 13 | "github.com/katzenpost/hpqc/sign" 14 | "github.com/katzenpost/hpqc/sign/ed25519" 15 | "github.com/katzenpost/hpqc/sign/hybrid" 16 | "github.com/katzenpost/hpqc/sign/sphincsplus" 17 | ) 18 | 19 | var potentialSchemes = [...]sign.Scheme{ 20 | // post quantum 21 | sphincsplus.Scheme(), 22 | 23 | // post quantum hybrids 24 | hybrid.Ed25519Sphincs, 25 | hybrid.Ed448Sphincs, 26 | } 27 | 28 | var allSchemes = []sign.Scheme{ 29 | // classical 30 | ed25519.Scheme(), 31 | ed448.Scheme(), 32 | 33 | // hybrid post quantum 34 | eddilithium2.Scheme(), 35 | eddilithium3.Scheme(), 36 | } 37 | 38 | var allSchemeNames map[string]sign.Scheme 39 | 40 | func init() { 41 | allSchemeNames = make(map[string]sign.Scheme) 42 | 43 | for _, scheme := range potentialSchemes { 44 | if scheme != nil { 45 | allSchemes = append(allSchemes, scheme) 46 | } 47 | } 48 | for _, scheme := range allSchemes { 49 | allSchemeNames[strings.ToLower(scheme.Name())] = scheme 50 | } 51 | } 52 | 53 | // ByName returns the NIKE scheme by string name. 54 | func ByName(name string) sign.Scheme { 55 | ret := allSchemeNames[strings.ToLower(name)] 56 | return ret 57 | } 58 | 59 | // All returns all signature schemes supported. 60 | func All() []sign.Scheme { 61 | a := allSchemes 62 | return a[:] 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-golang-cross-compile-test.yml: -------------------------------------------------------------------------------- 1 | name: Go cross compile 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ "main" ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | CC: ["clang"] 14 | ARCH: ["arm32v5", "arm32v7", "i386", "arm64", "mips64", "mips64le", "mips", "mipsle", "ppc64", "ppc64le", "s390x"] 15 | go-version: [ '1.21.x', '1.24.x' ] 16 | exclude: 17 | # Exclude failing non-primary platform combinations 18 | - ARCH: "arm32v7" 19 | go-version: "1.21.x" 20 | - ARCH: "arm64" 21 | go-version: "1.21.x" 22 | - ARCH: "ppc64le" 23 | go-version: "1.21.x" 24 | - ARCH: "mipsle" 25 | go-version: "1.24.x" 26 | - ARCH: "s390x" 27 | go-version: "1.24.x" 28 | - ARCH: "mips64le" 29 | go-version: "1.21.x" 30 | fail-fast: false 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Setup Go ${{ matrix.go-version }} 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: ${{ matrix.go-version }} 37 | - name: Display Go version 38 | run: go version 39 | - name: Install Golang deps 40 | run: sudo ./misc/install-debian-go-deps-by-arch.sh ${{ matrix.ARCH }} 41 | - name: Install Go (from go.mod) 42 | uses: actions/setup-go@v4 43 | with: 44 | go-version-file: go.mod 45 | check-latest: true 46 | - name: Run tests 47 | run: go test -v ./... 48 | env: 49 | CGO_LDFLAGS: -Wl,-z,stack-size=0x1F40000 50 | -------------------------------------------------------------------------------- /rand/deterministic_rand_test.go: -------------------------------------------------------------------------------- 1 | // deterministic_rand_test.go - Katzenpost deterministic rand.Reader tests. 2 | // Copyright (C) 2018 David Stainton. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package rand 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | // XXX fix this stupid test to actually test something 26 | func TestPerm(t *testing.T) { 27 | assert := assert.New(t) 28 | 29 | skey := [32]byte{0x00} 30 | rand, err := NewDeterministicRandReader(skey[:]) 31 | assert.NoError(err, "wtf") 32 | rand2, err := NewDeterministicRandReader(skey[:]) 33 | assert.NoError(err, "wtf") 34 | 35 | for i := 0; i < 42; i++ { 36 | tmp := [6]byte{} 37 | tmp2 := [6]byte{} 38 | _, err = rand.Read(tmp[:]) 39 | assert.NoError(err, "wtf") 40 | _, err = rand2.Read(tmp2[:]) 41 | assert.NoError(err, "wtf") 42 | assert.True(tmp == tmp2) 43 | t.Logf("rand values are %x", tmp) 44 | } 45 | for i := 1; i < 42; i++ { 46 | j := rand.Int63() 47 | t.Logf("%v", j) 48 | assert.True(j >= 0) 49 | } 50 | for i := 0; i < 42; i++ { 51 | p := rand.Perm(i) 52 | t.Logf("%v %v %v", i, len(p), p) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kem/schemes/kem_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package schemes 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/katzenpost/hpqc/kem" 8 | ) 9 | 10 | func BenchmarkKeyGen(b *testing.B) { 11 | var pubkey kem.PublicKey 12 | var privkey kem.PrivateKey 13 | var err error 14 | 15 | schemes := All() 16 | 17 | for _, s := range schemes { 18 | b.Run(s.Name(), func(b *testing.B) { 19 | for i := 0; i < b.N; i++ { 20 | pubkey, privkey, err = s.GenerateKeyPair() 21 | if err != nil { 22 | b.Fatal(err) 23 | } 24 | } 25 | }) 26 | } 27 | 28 | pubkey.Scheme() 29 | privkey.Scheme() 30 | } 31 | 32 | func BenchmarkEncap(b *testing.B) { 33 | schemes := All() 34 | 35 | for _, s := range schemes { 36 | pubkey, _, err := s.GenerateKeyPair() 37 | if err != nil { 38 | b.Fatal(err) 39 | } 40 | 41 | var ct []byte 42 | var ss []byte 43 | 44 | b.Run(s.Name(), func(b *testing.B) { 45 | for i := 0; i < b.N; i++ { 46 | ct, ss, err = s.Encapsulate(pubkey) 47 | if err != nil { 48 | b.Fatal(err) 49 | } 50 | } 51 | }) 52 | 53 | ct2 := make([]byte, len(ct)) 54 | copy(ct2, ct) 55 | 56 | ss2 := make([]byte, len(ss)) 57 | copy(ss2, ss) 58 | } 59 | 60 | } 61 | 62 | func BenchmarkDecaps(b *testing.B) { 63 | schemes := All() 64 | 65 | for _, s := range schemes { 66 | pubkey, privkey, err := s.GenerateKeyPair() 67 | if err != nil { 68 | b.Fatal(err) 69 | } 70 | 71 | ct, ss, err := s.Encapsulate(pubkey) 72 | if err != nil { 73 | b.Fatal(err) 74 | } 75 | 76 | b.Run(s.Name(), func(b *testing.B) { 77 | for i := 0; i < b.N; i++ { 78 | ss2, err := s.Decapsulate(privkey, ct) 79 | if err != nil { 80 | b.Fatal(err) 81 | } 82 | if !bytes.Equal(ss, ss2) { 83 | b.Fatal("decapsulated shared secret mismatch error") 84 | } 85 | } 86 | }) 87 | 88 | ct2 := make([]byte, len(ct)) 89 | copy(ct2, ct) 90 | 91 | ss2 := make([]byte, len(ss)) 92 | copy(ss2, ss) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /kem/mkem/ciphertext.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package mkem provides multiparty KEM construction. 5 | package mkem 6 | 7 | import ( 8 | "github.com/fxamacker/cbor/v2" 9 | 10 | "github.com/katzenpost/hpqc/nike" 11 | ) 12 | 13 | var ( 14 | // Create reusable EncMode interface with immutable options, safe for concurrent use. 15 | ccbor cbor.EncMode 16 | ) 17 | 18 | type Ciphertext struct { 19 | EphemeralPublicKey nike.PublicKey 20 | DEKCiphertexts []*[DEKSize]byte 21 | Envelope []byte 22 | } 23 | 24 | type IntermediaryCiphertext struct { 25 | EphemeralPublicKey []byte 26 | DEKCiphertexts []*[DEKSize]byte 27 | Envelope []byte 28 | } 29 | 30 | func (i *IntermediaryCiphertext) Bytes() []byte { 31 | blob, err := ccbor.Marshal(i) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return blob 36 | } 37 | 38 | func (i *IntermediaryCiphertext) FromBytes(b []byte) error { 39 | err := cbor.Unmarshal(b, i) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func CiphertextFromBytes(scheme *Scheme, b []byte) (*Ciphertext, error) { 47 | ic := &IntermediaryCiphertext{} 48 | err := ic.FromBytes(b) 49 | if err != nil { 50 | return nil, err 51 | } 52 | pubkey, err := scheme.nike.UnmarshalBinaryPublicKey(ic.EphemeralPublicKey) 53 | if err != nil { 54 | return nil, err 55 | } 56 | c := &Ciphertext{ 57 | EphemeralPublicKey: pubkey, 58 | DEKCiphertexts: ic.DEKCiphertexts, 59 | Envelope: ic.Envelope, 60 | } 61 | return c, nil 62 | } 63 | 64 | func (m *Ciphertext) Marshal() []byte { 65 | ic := &IntermediaryCiphertext{ 66 | EphemeralPublicKey: m.EphemeralPublicKey.Bytes(), 67 | DEKCiphertexts: m.DEKCiphertexts, 68 | Envelope: m.Envelope, 69 | } 70 | return ic.Bytes() 71 | } 72 | 73 | func init() { 74 | var err error 75 | opts := cbor.CanonicalEncOptions() 76 | ccbor, err = opts.EncMode() 77 | if err != nil { 78 | panic(err) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sign/sphincsplus/sphincs_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // SPDX-FileCopyrightText: (c) 2022-2024 David Stainton 4 | // SPDX-License-Identifier: AGPL-3.0-only 5 | 6 | package sphincsplus 7 | 8 | import ( 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestSignatureScheme(t *testing.T) { 15 | t.Parallel() 16 | 17 | pubKey, privKey, err := Scheme().GenerateKey() 18 | require.NoError(t, err) 19 | message := []byte("i am a message") 20 | sig := Scheme().Sign(privKey, message, nil) 21 | require.True(t, Scheme().Verify(pubKey, message, sig, nil)) 22 | } 23 | 24 | func TestSerialization(t *testing.T) { 25 | t.Parallel() 26 | 27 | pubKey, privKey, err := Scheme().GenerateKey() 28 | require.NoError(t, err) 29 | 30 | message := []byte("i am a message") 31 | sig := Scheme().Sign(privKey, message, nil) 32 | 33 | pubKeyBytes, err := pubKey.MarshalBinary() 34 | require.NoError(t, err) 35 | 36 | pubKey2, err := Scheme().UnmarshalBinaryPublicKey(pubKeyBytes) 37 | require.NoError(t, err) 38 | 39 | pubKey2Bytes, err := pubKey2.MarshalBinary() 40 | require.NoError(t, err) 41 | require.Equal(t, pubKey2Bytes, pubKeyBytes) 42 | require.True(t, Scheme().Verify(pubKey, message, sig, nil)) 43 | } 44 | 45 | func TestSizes(t *testing.T) { 46 | t.Parallel() 47 | 48 | pubKey, privKey, err := Scheme().GenerateKey() 49 | require.NoError(t, err) 50 | 51 | message := []byte("i am a message") 52 | sig := Scheme().Sign(privKey, message, nil) 53 | require.True(t, Scheme().Verify(pubKey, message, sig, nil)) 54 | 55 | privKeyBlob, err := privKey.MarshalBinary() 56 | require.NoError(t, err) 57 | 58 | pubKeyBlob, err := pubKey.MarshalBinary() 59 | require.NoError(t, err) 60 | 61 | t.Logf("privKey len %d", len(privKeyBlob)) 62 | t.Logf("pubKey len %d", len(pubKeyBlob)) 63 | t.Logf("sig len %d", len(sig)) 64 | 65 | require.Equal(t, len(privKeyBlob), Scheme().PrivateKeySize()) 66 | require.Equal(t, len(pubKeyBlob), Scheme().PublicKeySize()) 67 | require.Equal(t, len(sig), Scheme().SignatureSize()) 68 | } 69 | -------------------------------------------------------------------------------- /kem/sntrup/sntrup_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2023 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package sntrup 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/katzenpost/hpqc/util" 12 | ) 13 | 14 | func TestSNTRUPKEM(t *testing.T) { 15 | s := Scheme() 16 | 17 | t.Logf("ciphertext size %d", s.CiphertextSize()) 18 | t.Logf("shared key size %d", s.SharedKeySize()) 19 | t.Logf("private key size %d", s.PrivateKeySize()) 20 | t.Logf("public key size %d", s.PublicKeySize()) 21 | t.Logf("seed size %d", s.SeedSize()) 22 | 23 | pubkey1, privkey1, err := s.GenerateKeyPair() 24 | require.NoError(t, err) 25 | pubkey2, privkey2, err := s.GenerateKeyPair() 26 | require.NoError(t, err) 27 | 28 | require.False(t, pubkey1.Equal(pubkey2)) 29 | require.False(t, privkey1.Equal(privkey2)) 30 | 31 | pubKey1Blob, err := pubkey1.MarshalBinary() 32 | require.NoError(t, err) 33 | 34 | pubkey3, err := s.UnmarshalBinaryPublicKey(pubKey1Blob) 35 | require.NoError(t, err) 36 | require.True(t, pubkey3.Equal(pubkey1)) 37 | 38 | privKey1Blob, err := privkey1.MarshalBinary() 39 | require.NoError(t, err) 40 | privkey3, err := s.UnmarshalBinaryPrivateKey(privKey1Blob) 41 | require.NoError(t, err) 42 | require.True(t, privkey3.Equal(privkey1)) 43 | 44 | ct1, ss1, err := s.Encapsulate(pubkey1) 45 | require.NoError(t, err) 46 | require.False(t, util.CtIsZero(ss1)) 47 | require.False(t, util.CtIsZero(ct1)) 48 | 49 | ss1b, err := s.Decapsulate(privkey1, ct1) 50 | require.NoError(t, err) 51 | require.Equal(t, ss1, ss1b) 52 | t.Logf("our shared key is %x", ss1) 53 | 54 | ct2, ss2, err := s.Encapsulate(pubkey1) 55 | require.NoError(t, err) 56 | require.NotEqual(t, ct1, ct2) 57 | require.NotEqual(t, ss1, ss2) 58 | 59 | seed := make([]byte, s.SeedSize()) 60 | pubkey4, privkey4 := s.DeriveKeyPair(seed) 61 | ct3, ss3, err := s.Encapsulate(pubkey4) 62 | require.NoError(t, err) 63 | ss3b, err := s.Decapsulate(privkey4, ct3) 64 | require.NoError(t, err) 65 | require.Equal(t, ss3, ss3b) 66 | } 67 | -------------------------------------------------------------------------------- /nike/schemes/schemes.go: -------------------------------------------------------------------------------- 1 | package schemes 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/katzenpost/hpqc/nike" 7 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh1024" 8 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh2048" 9 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh511" 10 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh512" 11 | "github.com/katzenpost/hpqc/nike/hybrid" 12 | "github.com/katzenpost/hpqc/nike/x25519" 13 | "github.com/katzenpost/hpqc/nike/x448" 14 | "github.com/katzenpost/hpqc/rand" 15 | ) 16 | 17 | var potentialSchemes = [...]nike.Scheme{ 18 | 19 | // post quantum NIKE schemes 20 | ctidh511.Scheme(), 21 | ctidh512.Scheme(), 22 | ctidh1024.Scheme(), 23 | ctidh2048.Scheme(), 24 | 25 | // hybrid NIKE schemes 26 | 27 | // see ticket https://github.com/katzenpost/hpqc/issues/34 28 | //hybrid.CTIDH511X25519, 29 | 30 | hybrid.CTIDH512X25519, 31 | hybrid.CTIDH512X448, 32 | hybrid.CTIDH1024X25519, 33 | hybrid.CTIDH1024X448, 34 | hybrid.CTIDH2048X448, 35 | 36 | // NOBS CSIDH doesn't work on arm32 37 | // XXX TODO: deprecate and remove. 38 | hybrid.NOBS_CSIDH512X25519, 39 | } 40 | 41 | var allSchemes = []nike.Scheme{ 42 | 43 | // classical NIKE schemes 44 | x25519.Scheme(rand.Reader), 45 | x448.Scheme(rand.Reader), 46 | 47 | // Classical DiffieHellman imeplementation has a bug with this ticket: 48 | // https://github.com/katzenpost/hpqc/issues/39 49 | //diffiehellman.Scheme(), 50 | } 51 | 52 | var allSchemeNames map[string]nike.Scheme 53 | 54 | func init() { 55 | allSchemeNames = make(map[string]nike.Scheme) 56 | for _, scheme := range potentialSchemes { 57 | if scheme != nil { 58 | allSchemes = append(allSchemes, scheme) 59 | } 60 | } 61 | for _, scheme := range allSchemes { 62 | allSchemeNames[strings.ToLower(scheme.Name())] = scheme 63 | } 64 | } 65 | 66 | // ByName returns the NIKE scheme by string name. 67 | func ByName(name string) nike.Scheme { 68 | return allSchemeNames[strings.ToLower(name)] 69 | } 70 | 71 | // All returns all NIKE schemes supported. 72 | func All() []nike.Scheme { 73 | a := allSchemes 74 | return a[:] 75 | } 76 | -------------------------------------------------------------------------------- /examples/nike_and_cipher/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2025 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // standalone NIKE + symmetric cipher example 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | 11 | "github.com/agl/gcmsiv" 12 | "golang.org/x/crypto/blake2b" 13 | 14 | "github.com/katzenpost/hpqc/nike/schemes" 15 | "github.com/katzenpost/hpqc/rand" 16 | ) 17 | 18 | func main() { 19 | // pick a NIKE scheme 20 | scheme := schemes.ByName("x25519") 21 | 22 | // Alice and Bob generate key pairs 23 | // and exchange their public keys 24 | alicePub, alicePriv, err := scheme.GenerateKeyPairFromEntropy(rand.Reader) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | bobPub, bobPriv, err := scheme.GenerateKeyPairFromEntropy(rand.Reader) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | // Alice sends a message to Bob 35 | aliceSharedSecret := scheme.DeriveSecret(alicePriv, bobPub) 36 | aliceSecretHash := blake2b.Sum256(aliceSharedSecret) 37 | aliceCipher, err := gcmsiv.NewGCMSIV(aliceSecretHash[:]) 38 | if err != nil { 39 | panic(err) 40 | } 41 | // encrypt with AES-GCM-SIV: 42 | alicePlaintext := []byte("yo, what's up?") 43 | nonce := make([]byte, aliceCipher.NonceSize()) 44 | _, err = rand.Reader.Read(nonce) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | aliceCiphertext := aliceCipher.Seal([]byte{}, nonce, alicePlaintext, []byte{}) 50 | 51 | fmt.Printf("Alice's message: %s\nEncrypted as: %x\n", alicePlaintext, aliceCiphertext) 52 | 53 | // we pretend Alice sends Bob the nonce and ciphertext 54 | // Bob decrypts ciphertext from Alice 55 | 56 | bobSharedSecret := scheme.DeriveSecret(bobPriv, alicePub) 57 | 58 | // sanity check 59 | if !bytes.Equal(aliceSharedSecret, bobSharedSecret) { 60 | panic("shared secrets must be equal") 61 | } 62 | 63 | bobSecretHash := blake2b.Sum256(aliceSharedSecret) 64 | bobCipher, err := gcmsiv.NewGCMSIV(bobSecretHash[:]) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | bobPlaintext, err := bobCipher.Open([]byte{}, nonce, aliceCiphertext, []byte{}) 70 | 71 | // sanity check 72 | if !bytes.Equal(alicePlaintext, bobPlaintext) { 73 | panic("plaintexts must be equal") 74 | } 75 | 76 | fmt.Printf("Bob decrypts ciphertext into plaintext: %s\n", bobPlaintext) 77 | } 78 | -------------------------------------------------------------------------------- /examples/kem_and_cipher/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2025 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // standalone KEM + symmetric cipher example 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | 11 | "github.com/agl/gcmsiv" 12 | "golang.org/x/crypto/blake2b" 13 | 14 | "github.com/katzenpost/hpqc/kem/schemes" 15 | "github.com/katzenpost/hpqc/rand" 16 | ) 17 | 18 | func main() { 19 | // pick a KEM scheme 20 | scheme := schemes.ByName("Xwing") 21 | 22 | // Bob generate key pairs 23 | // and we pretend he sends his public key to Alice. 24 | // Alice doesn't need a keypair because this is 25 | // an example using a KEM and not a NIKE. 26 | bobPub, bobPriv, err := scheme.GenerateKeyPair() 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // Alice would like to send Bob an encrypted message 32 | aliceKEMCiphertext, aliceSharedSecret, err := scheme.Encapsulate(bobPub) 33 | if err != nil { 34 | panic(err) 35 | } 36 | aliceSecretHash := blake2b.Sum256(aliceSharedSecret) 37 | aliceCipher, err := gcmsiv.NewGCMSIV(aliceSecretHash[:]) 38 | if err != nil { 39 | panic(err) 40 | } 41 | alicePlaintext := []byte("yo, what's up?") 42 | nonce := make([]byte, aliceCipher.NonceSize()) 43 | _, err = rand.Reader.Read(nonce) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | aliceCiphertext := aliceCipher.Seal([]byte{}, nonce, alicePlaintext, []byte{}) 49 | 50 | fmt.Printf("Alice's message: %s\nEncrypted as: %x\n", alicePlaintext, aliceCiphertext) 51 | 52 | // we pretend Alice sends Bob the following: 53 | // aliceKEMCiphertext, aliceCiphertext, nonce 54 | 55 | bobSharedSecret, err := scheme.Decapsulate(bobPriv, aliceKEMCiphertext) 56 | if err != nil { 57 | panic(err) 58 | } 59 | // sanity check 60 | if !bytes.Equal(aliceSharedSecret, bobSharedSecret) { 61 | panic("shared secrets must be equal") 62 | } 63 | 64 | bobSecretHash := blake2b.Sum256(aliceSharedSecret) 65 | bobCipher, err := gcmsiv.NewGCMSIV(bobSecretHash[:]) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | bobPlaintext, err := bobCipher.Open([]byte{}, nonce, aliceCiphertext, []byte{}) 71 | 72 | // sanity check 73 | if !bytes.Equal(alicePlaintext, bobPlaintext) { 74 | panic("plaintexts must be equal") 75 | } 76 | 77 | fmt.Printf("Bob decrypts ciphertext into plaintext: %s\n", bobPlaintext) 78 | } 79 | -------------------------------------------------------------------------------- /.github/workflows/windows-msys-64bit-gcc-ucrt-msvcrt-golang-test.yml: -------------------------------------------------------------------------------- 1 | name: Windows Msys2 64bit (cygwin,msvcrt,ucrt) gcc golang build and test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | windows-build-and-test-golang: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | OS: ["windows-2022"] 11 | CC: ["gcc"] 12 | ENVIRONMENT: ["UCRT64", "MINGW64"] # https://www.msys2.org/docs/environments/ 13 | go-version: ["1.23.0"] 14 | fail-fast: false 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Msys2 19 | uses: msys2/setup-msys2@v2 20 | with: 21 | msystem: ${{ matrix.ENVIRONMENT }} 22 | install: >- 23 | base-devel 24 | mingw-w64-x86_64-toolchain 25 | mingw-w64-x86_64-pkg-config 26 | mingw-w64-x86_64-gcc 27 | mingw-w64-ucrt-x86_64-gcc 28 | mingw-w64-x86_64-go 29 | mingw-w64-ucrt-x86_64-go 30 | make 31 | git 32 | gcc 33 | 34 | - name: Setup Go ${{ matrix.go-version }} 35 | uses: actions/setup-go@v5 36 | with: 37 | go-version: ${{ matrix.go-version }} 38 | 39 | - name: Gather runtime environment 40 | shell: msys2 {0} 41 | run: | 42 | echo ${{ matrix.ENVIRONMENT }} 43 | uname -a 44 | bash --version 45 | ${{ matrix.CC }} -v 46 | go version 47 | 48 | - name: Install golang dependencies 49 | shell: msys2 {0} 50 | run: | 51 | export HIGHCTIDH_PORTABLE=1 52 | export CGO_ENABLED=1 53 | go get -v ./... 54 | 55 | - name: Build golang 56 | shell: msys2 {0} 57 | run: | 58 | export HIGHCTIDH_PORTABLE=1 59 | export CGO_ENABLED=1 60 | export GOEXPERIMENT=cgocheck2 61 | export GODEBUG=cgocheck=1 62 | export CGO_LDFLAGS="-Wl,--no-as-needed -Wl,-allow-multiple-definition" 63 | go build -v ./... 64 | 65 | - name: Golang test 66 | shell: msys2 {0} 67 | run: | 68 | export HIGHCTIDH_PORTABLE=1 69 | export CGO_ENABLED=1 70 | export GOEXPERIMENT=cgocheck2 71 | export GODEBUG=cgocheck=1 72 | export CGO_LDFLAGS="-Wl,--no-as-needed -Wl,-allow-multiple-definition" 73 | go test -v ./... 74 | 75 | -------------------------------------------------------------------------------- /misc/install-debian-go-deps-by-arch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e; 3 | 4 | ARCH=$1; 5 | # This BASE_PACKAGES list assumes a Docker image with golang installed 6 | BASE_PACKAGES="ca-certificates clang git make"; 7 | 8 | if [ -n "$ARCH" ]; 9 | then 10 | if [ "$ARCH" == "386" ] || [ "$ARCH" == "i386" ] || [ "$ARCH" == "amd64" ]; 11 | then 12 | dpkg --add-architecture i386; 13 | PACKAGES="$BASE_PACKAGES libc6-i386 libc6-dev linux-libc-dev linux-libc-dev:i386 libc6-dev-i386 libc6-i386-cross libc6 libc6-dev"; 14 | fi 15 | 16 | if [ "$ARCH" == "arm" ] || [ "$ARCH" == "arm32v5" ] || [ "$ARCH" == "arm32v6" ] || [ "$ARCH" == "arm32v7" ]; 17 | then 18 | PACKAGES="$BASE_PACKAGES libc6-armel-cross libc6-armhf-cross libc6-dev-armel-cross libc6-dev-armhf-cross"; 19 | fi 20 | 21 | if [ "$ARCH" == "arm64" ]; 22 | then 23 | PACKAGES="$BASE_PACKAGES libc6-arm64-cross libc6-dev-arm64-cross"; 24 | fi 25 | 26 | if [ "$ARCH" == "mips" ]; 27 | then 28 | PACKAGES="$BASE_PACKAGES libc6-dev-mips-cross linux-libc-dev-mips-cross"; 29 | fi 30 | 31 | if [ "$ARCH" == "mipsle" ] || [ "$ARCH" == "mipsel" ]; 32 | then 33 | PACKAGES="$BASE_PACKAGES libc6-dev-mipsel-cross linux-libc-dev-mipsel-cross"; 34 | fi 35 | 36 | if [ "$ARCH" == "mips64" ]; 37 | then 38 | PACKAGES="$BASE_PACKAGES libc6-dev-mips64-cross linux-libc-dev-mips64-cross"; 39 | fi 40 | 41 | if [ "$ARCH" == "mips64le" ] || [ "$ARCH" == "mips64el" ]; 42 | then 43 | PACKAGES="$BASE_PACKAGES libc6-dev-mipsn32-mips64el-cross linux-libc-dev-mips64el-cross"; 44 | fi 45 | 46 | if [ "$ARCH" == "ppc64" ]; 47 | then 48 | PACKAGES="$BASE_PACKAGES libc6-dev-powerpc-ppc64-cross libc6-dev-ppc64-cross linux-libc-dev-ppc64-cross "; 49 | fi 50 | 51 | if [ "$ARCH" == "ppc64le" ]; 52 | then 53 | PACKAGES="$BASE_PACKAGES libc6-dev-ppc64el-cross linux-libc-dev-ppc64el-cross"; 54 | fi 55 | 56 | if [ "$ARCH" == "riscv64" ]; 57 | then 58 | PACKAGES="$BASE_PACKAGES libc6-dev-riscv64-cross"; 59 | fi 60 | 61 | if [ "$ARCH" == "s390x" ]; 62 | then 63 | PACKAGES="$BASE_PACKAGES libc6-dev-s390x-cross"; 64 | fi 65 | else 66 | echo "ARCH appears to be unset: ARCH=$ARCH"; 67 | exit 1; 68 | fi 69 | 70 | apt update > /dev/null 2>&1; 71 | echo "Installing required packages for $ARCH: $PACKAGES"; 72 | apt install -y --no-install-recommends $PACKAGES > /dev/null 2>&1; 73 | echo "Required packages installed"; 74 | -------------------------------------------------------------------------------- /rand/rand_test.go: -------------------------------------------------------------------------------- 1 | // rand_test.go - Random number tests. 2 | // Copyright (C) 2016 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package rand 18 | 19 | import ( 20 | "bytes" 21 | "compress/zlib" 22 | "fmt" 23 | "io" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func ensureHighEntropy(b []byte) error { 30 | var zipBuf bytes.Buffer 31 | zipper := zlib.NewWriter(&zipBuf) 32 | if _, err := zipper.Write(b); err != nil { 33 | return err 34 | } 35 | zipper.Close() 36 | 37 | errorThresh := int(float32(len(b)) * 0.95) 38 | if zipBuf.Len()-16 < errorThresh { 39 | return fmt.Errorf("random data noticably compressed????: %v", zipBuf.Len()) 40 | } 41 | return nil 42 | } 43 | 44 | func tryRandomRead(n int) error { 45 | b := make([]byte, n) 46 | rd, err := io.ReadFull(Reader, b) 47 | if err != nil { 48 | return err 49 | } 50 | if rd != len(b) { 51 | return fmt.Errorf("truncated read: %v", rd) 52 | } 53 | 54 | bCmp := make([]byte, n) 55 | _, err = io.ReadFull(Reader, bCmp) 56 | if err != nil { 57 | return err 58 | } 59 | if bytes.Equal(b, bCmp) { 60 | return fmt.Errorf("repeated calls produced identical output") 61 | } 62 | 63 | // Statistical test... 64 | return ensureHighEntropy(b[:]) 65 | } 66 | 67 | func TestImprovedSyscallRand(t *testing.T) { 68 | if !usingImprovedSyscallEntropy { 69 | t.Skip("Improved (non-broken) syscall entropy not supported") 70 | } 71 | 72 | // Short read. 73 | if err := tryRandomRead(256); err != nil { 74 | t.Errorf("short: %v", err) 75 | } 76 | 77 | // Large read. 78 | if err := tryRandomRead(1024); err != nil { 79 | t.Errorf("large: %v", err) 80 | } 81 | } 82 | 83 | func TestMath(t *testing.T) { 84 | assert := assert.New(t) 85 | 86 | mrand := NewMath() 87 | 88 | // Basic statistical test. 89 | var b [1024]byte 90 | for i := range b { 91 | b[i] = byte(mrand.Intn(256)) 92 | } 93 | assert.NoError(ensureHighEntropy(b[:]), "math/rand: Statistical test") 94 | } 95 | -------------------------------------------------------------------------------- /rand/deterministic_rand_reader.go: -------------------------------------------------------------------------------- 1 | // rand.go - 2 | // Copyright (C) 2018 David Stainton. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package rand 18 | 19 | import ( 20 | "encoding/binary" 21 | "math/rand" 22 | 23 | "github.com/katzenpost/chacha20" 24 | ) 25 | 26 | // DeterministicRandReader is a random Reader whose output is a chacha20 keystream. 27 | type DeterministicRandReader struct { 28 | cipher *chacha20.Cipher 29 | key []byte 30 | } 31 | 32 | // NewDeterministicRandReader returns a DeterministicRandReader initialized with key. 33 | func NewDeterministicRandReader(key []byte) (*DeterministicRandReader, error) { 34 | var nonce [8]byte 35 | cipher, err := chacha20.New(key, nonce[:]) 36 | if err != nil { 37 | return nil, err 38 | } 39 | reader := DeterministicRandReader{ 40 | cipher: cipher, 41 | key: key, 42 | } 43 | return &reader, err 44 | } 45 | 46 | // Read writes the keystream into the passed byteslice and returns the number of bytes written. 47 | func (r *DeterministicRandReader) Read(data []byte) (int, error) { 48 | readLen := len(data) 49 | r.cipher.KeyStream(data) 50 | return readLen, nil 51 | } 52 | 53 | // Int63 returns a random int64 with most significant bit set to 0. 54 | func (r *DeterministicRandReader) Int63() int64 { 55 | tmp := [8]byte{} 56 | _, err := r.Read(tmp[:]) 57 | if err != nil { 58 | panic(err) 59 | } 60 | // gotta chop those sign bits! 61 | tmp[7] = tmp[7] & 0x7F 62 | return int64(binary.LittleEndian.Uint64(tmp[:])) 63 | } 64 | 65 | // Seed initializes the DeterministicRandReader with nonce. 66 | func (r *DeterministicRandReader) Seed(seed int64) { 67 | var nonce [8]byte 68 | var err error 69 | count := binary.PutUvarint(nonce[:], uint64(seed)) 70 | if int64(count) != seed { 71 | panic("wtf") 72 | } 73 | r.cipher, err = chacha20.New(r.key, nonce[:]) 74 | if err != nil { 75 | panic(err) 76 | } 77 | } 78 | 79 | // Perm returns the shuffled slice of integers from 0 to n. 80 | func (r *DeterministicRandReader) Perm(n int) []int { 81 | p := rand.New(r) 82 | return p.Perm(n) 83 | } 84 | -------------------------------------------------------------------------------- /rand/rand_reader.go: -------------------------------------------------------------------------------- 1 | // rand_reader.go - `crypto/rand.Reader` replacement 2 | // Copyright (C) 2016 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package rand 18 | 19 | import ( 20 | "io" 21 | 22 | "github.com/katzenpost/hpqc/util" 23 | "golang.org/x/crypto/blake2b" 24 | ) 25 | 26 | // At least as of Go 1.6.1, Go's crypto/rand does some horrific bullshit that 27 | // defeats the point of getrandom(2), namedly, it cowardly refuses to use the 28 | // syscall based entropy source if it would have blocked on the first call. 29 | // 30 | // This is rather suboptimal. The correct thing to do for something named 31 | // "crypto/rand" is to fucking BLOCK if the entropy pool isn't there and not 32 | // to pull poor quality entropy by falling back to doing blocking reads on 33 | // "/dev/urandom". 34 | // 35 | // This was brought up in https://github.com/golang/go/issues/11833 36 | // and dismissed, I think they're wrong, I'm fixing it on common systems 37 | // that I care about. 38 | // 39 | // Upstream has seen the errors of their ways and fixed this as of Go 1.9 40 | // (See: https://github.com/golang/go/issues/19274), but that isn't guaranteed 41 | // to be everywhere, and there's an advantage to whitening the output anyway. 42 | 43 | const xofEntropySize = 32 44 | 45 | var ( 46 | // Reader is a replacement for crypto/rand.Reader. 47 | Reader io.Reader 48 | 49 | usingImprovedSyscallEntropy = false 50 | xofKey [xofEntropySize]byte 51 | ) 52 | 53 | type nonShitRandReader struct { 54 | getentropyFn func([]byte) error 55 | } 56 | 57 | func (r *nonShitRandReader) Read(b []byte) (int, error) { 58 | blen := len(b) 59 | switch { 60 | case blen == 0: 61 | return 0, nil 62 | } 63 | 64 | // Whiten the output using BLAKE2Xb. 65 | var xofEntropy [xofEntropySize]byte 66 | xof, err := blake2b.NewXOF(uint32(len(b)), xofKey[:]) 67 | if err != nil { 68 | return 0, err 69 | } 70 | defer func() { 71 | xof.Reset() 72 | util.ExplicitBzero(xofEntropy[:]) 73 | }() 74 | if err := r.getentropyFn(xofEntropy[:]); err != nil { 75 | return 0, err 76 | } 77 | if _, err := xof.Write(xofEntropy[:]); err != nil { 78 | return 0, err 79 | } 80 | return xof.Read(b) 81 | } 82 | 83 | func initWhitening() { 84 | if _, err := Reader.Read(xofKey[:]); err != nil { 85 | panic("BUG: failed to initialize XOF key: " + err.Error()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /util/pem/pem.go: -------------------------------------------------------------------------------- 1 | // pem.go - PEM file write barrier. 2 | // 3 | // Copyright (C) 2022 David Stainton. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as 7 | // published by the Free Software Foundation, either version 3 of the 8 | // License, or (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package pem 19 | 20 | import ( 21 | "encoding/pem" 22 | "errors" 23 | "fmt" 24 | "os" 25 | "strings" 26 | 27 | "github.com/katzenpost/hpqc/util" 28 | ) 29 | 30 | // KeyMaterial 31 | type KeyMaterial interface { 32 | FromBytes([]byte) error 33 | 34 | Bytes() []byte 35 | 36 | KeyType() string 37 | } 38 | 39 | func ToPEMString(key KeyMaterial) string { 40 | return string(ToPEMBytes(key)) 41 | } 42 | 43 | func ToPEMBytes(key KeyMaterial) []byte { 44 | keyType := strings.ToUpper(key.KeyType()) 45 | if util.CtIsZero(key.Bytes()) { 46 | panic(fmt.Sprintf("ToPEMString/%s: attempted to serialize scrubbed key", keyType)) 47 | } 48 | blk := &pem.Block{ 49 | Type: keyType, 50 | Bytes: key.Bytes(), 51 | } 52 | return pem.EncodeToMemory(blk) 53 | } 54 | 55 | func ToFile(f string, key KeyMaterial) error { 56 | out, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 57 | if err != nil { 58 | return err 59 | } 60 | outBuf := ToPEMBytes(key) 61 | writeCount, err := out.Write(outBuf) 62 | if err != nil { 63 | return err 64 | } 65 | if writeCount != len(outBuf) { 66 | return errors.New("partial write failure") 67 | } 68 | err = out.Sync() 69 | if err != nil { 70 | return err 71 | } 72 | return out.Close() 73 | } 74 | 75 | func FromPEMString(s string, key KeyMaterial) error { 76 | return FromPEMBytes([]byte(s), key) 77 | } 78 | 79 | func FromPEMBytes(b []byte, key KeyMaterial) error { 80 | keyType := strings.ToUpper(key.KeyType()) 81 | 82 | blk, _ := pem.Decode(b) 83 | if blk == nil { 84 | return fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 85 | } 86 | if strings.ToUpper(blk.Type) != keyType { 87 | return fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 88 | } 89 | return key.FromBytes(blk.Bytes) 90 | } 91 | 92 | func FromFile(f string, key KeyMaterial) error { 93 | buf, err := os.ReadFile(f) 94 | if err != nil { 95 | return fmt.Errorf("pem.FromFile error: %s", err) 96 | } 97 | err = FromPEMBytes(buf, key) 98 | if err != nil { 99 | return fmt.Errorf("pem.FromFile failed to read from file %s, with buf len %d and err %s", f, len(buf), err) 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /kem/hybrid/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Cloudflare. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Cloudflare nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ======================================================================== 30 | 31 | Copyright (c) 2009 The Go Authors. All rights reserved. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions are 35 | met: 36 | 37 | * Redistributions of source code must retain the above copyright 38 | notice, this list of conditions and the following disclaimer. 39 | * Redistributions in binary form must reproduce the above 40 | copyright notice, this list of conditions and the following disclaimer 41 | in the documentation and/or other materials provided with the 42 | distribution. 43 | * Neither the name of Google Inc. nor the names of its 44 | contributors may be used to endorse or promote products derived from 45 | this software without specific prior written permission. 46 | 47 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 48 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 49 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 50 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 51 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 52 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 53 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 54 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 55 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 56 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 57 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | -------------------------------------------------------------------------------- /kem/schemes/kem_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package schemes 5 | 6 | import ( 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/katzenpost/hpqc/kem" 15 | "github.com/katzenpost/hpqc/kem/pem" 16 | "github.com/katzenpost/hpqc/rand" 17 | ) 18 | 19 | func TestKEMTextUnmarshal(t *testing.T) { 20 | todo := All() 21 | 22 | dir, err := ioutil.TempDir("", "example") 23 | require.NoError(t, err) 24 | 25 | testkem := func(s kem.Scheme) { 26 | 27 | pubkey, privkey, err := s.GenerateKeyPair() 28 | require.NoError(t, err) 29 | 30 | seed := make([]byte, s.SeedSize()) 31 | _, err = rand.Reader.Read(seed) 32 | require.NoError(t, err) 33 | pubkey, privkey = s.DeriveKeyPair(seed) 34 | 35 | blob1, err := pubkey.MarshalText() 36 | require.NoError(t, err) 37 | 38 | testpubkey2, err := s.UnmarshalTextPublicKey(blob1) 39 | require.NoError(t, err) 40 | 41 | blob2, err := testpubkey2.MarshalText() 42 | require.NoError(t, err) 43 | 44 | require.Equal(t, blob1, blob2) 45 | 46 | blob1, err = pubkey.MarshalBinary() 47 | require.NoError(t, err) 48 | require.Equal(t, s.PublicKeySize(), len(blob1)) 49 | 50 | // test private key marshaling/unmarshaling 51 | privfile := filepath.Join(dir, "privkey.pem") 52 | err = pem.PrivateKeyToFile(privfile, privkey) 53 | require.NoError(t, err) 54 | 55 | privkey2, err := pem.FromPrivatePEMFile(privfile, s) 56 | require.NoError(t, err) 57 | 58 | blob1, err = privkey.MarshalBinary() 59 | require.NoError(t, err) 60 | require.Equal(t, s.PrivateKeySize(), len(blob1)) 61 | 62 | blob2, err = privkey2.MarshalBinary() 63 | require.NoError(t, err) 64 | require.Equal(t, s.PrivateKeySize(), len(blob2)) 65 | 66 | require.Equal(t, blob1, blob2) 67 | require.True(t, privkey2.Equal(privkey)) 68 | 69 | err = os.Remove(privfile) 70 | require.NoError(t, err) 71 | } 72 | 73 | for _, scheme := range todo { 74 | if scheme.Name() == "DH4096_RFC3526" { 75 | t.Logf("skipping %s", scheme.Name()) 76 | continue 77 | } 78 | t.Logf("testing KEM Scheme: %s", scheme.Name()) 79 | t.Logf("PublicKeySize %d PrivateKeySize %d CiphertextSize %d", scheme.PublicKeySize(), scheme.PrivateKeySize(), scheme.CiphertextSize()) 80 | testkem(scheme) 81 | t.Log("OK") 82 | } 83 | } 84 | 85 | func TestKEMEncapDecap(t *testing.T) { 86 | todo := All() 87 | 88 | testkem := func(s kem.Scheme) { 89 | pubkey1, privkey1, err := s.GenerateKeyPair() 90 | require.NoError(t, err) 91 | 92 | ct1, ss1, err := s.Encapsulate(pubkey1) 93 | require.NoError(t, err) 94 | require.Equal(t, s.CiphertextSize(), len(ct1)) 95 | require.Equal(t, s.SharedKeySize(), len(ss1)) 96 | 97 | ss2, err := s.Decapsulate(privkey1, ct1) 98 | require.NoError(t, err) 99 | 100 | require.Equal(t, ss1, ss2) 101 | } 102 | 103 | for _, scheme := range todo { 104 | t.Logf("testing KEM Scheme: %s", scheme.Name()) 105 | t.Logf("PublicKeySize %d PrivateKeySize %d CiphertextSize %d", scheme.PublicKeySize(), scheme.PrivateKeySize(), scheme.CiphertextSize()) 106 | testkem(scheme) 107 | t.Log("OK") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /nike/nike.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package nike contains generic NIKE interfaces and many implementations. 5 | package nike 6 | 7 | import ( 8 | "encoding" 9 | "io" 10 | ) 11 | 12 | // Key is an interface for types encapsulating key material. 13 | type Key interface { 14 | encoding.BinaryMarshaler 15 | encoding.BinaryUnmarshaler 16 | encoding.TextMarshaler 17 | encoding.TextUnmarshaler 18 | 19 | // Reset resets the key material to all zeros. 20 | Reset() 21 | 22 | // Bytes serializes key material into a byte slice. 23 | Bytes() []byte 24 | 25 | // FromBytes loads key material from the given byte slice. 26 | FromBytes(data []byte) error 27 | } 28 | 29 | // PrivateKey is an interface for types encapsulating 30 | // private key material. 31 | type PrivateKey interface { 32 | Key 33 | 34 | Public() PublicKey 35 | } 36 | 37 | // PublicKey is an interface for types encapsulating 38 | // public key material. 39 | type PublicKey interface { 40 | Key 41 | 42 | // Blind performs a blinding operation and mutates the public 43 | // key with the blinded value. 44 | Blind(blindingFactor PrivateKey) error 45 | } 46 | 47 | // Scheme is an interface encapsulating a 48 | // non-interactive key exchange. 49 | type Scheme interface { 50 | 51 | // Name returns the name of the NIKE scheme implementation. 52 | Name() string 53 | 54 | // PublicKeySize returns the size in bytes of the public key. 55 | PublicKeySize() int 56 | 57 | // PrivateKeySize returns the size in bytes of the private key. 58 | PrivateKeySize() int 59 | 60 | // GeneratePrivateKey uses the given RNG to derive a new private key. 61 | // This can be used to deterministically generate private keys if the 62 | // entropy source is deterministic, for example an HKDF. 63 | GeneratePrivateKey(rng io.Reader) PrivateKey 64 | 65 | // GenerateKeyPair creates a new key pair. 66 | GenerateKeyPair() (PublicKey, PrivateKey, error) 67 | 68 | // GenerateKeyPairFromEntropy creates a new key pair from the given entropy source. 69 | GenerateKeyPairFromEntropy(rng io.Reader) (PublicKey, PrivateKey, error) 70 | 71 | // DeriveSecret derives a shared secret given a private key 72 | // from one party and a public key from another. 73 | DeriveSecret(PrivateKey, PublicKey) []byte 74 | 75 | // DerivePublicKey derives a public key given a private key. 76 | DerivePublicKey(PrivateKey) PublicKey 77 | 78 | // Blind performs the blinding operation against the 79 | // given group member, returning the blinded key. 80 | Blind(groupMember PublicKey, blindingFactor PrivateKey) (blindedGroupMember PublicKey) 81 | 82 | // NewEmptyPublicKey returns an uninitialized 83 | // PublicKey which is suitable to be loaded 84 | // via some serialization format via FromBytes 85 | // or FromPEMFile methods. 86 | NewEmptyPublicKey() PublicKey 87 | 88 | // NewEmptyPrivateKey returns an uninitialized 89 | // PrivateKey which is suitable to be loaded 90 | // via some serialization format via FromBytes 91 | // or FromPEMFile methods. 92 | NewEmptyPrivateKey() PrivateKey 93 | 94 | // UnmarshalBinaryPublicKey loads a public key from byte slice. 95 | UnmarshalBinaryPublicKey([]byte) (PublicKey, error) 96 | 97 | // Unmarshals a PrivateKey from the provided buffer. 98 | UnmarshalBinaryPrivateKey([]byte) (PrivateKey, error) 99 | } 100 | -------------------------------------------------------------------------------- /nike/x25519/ecdh_test.go: -------------------------------------------------------------------------------- 1 | // ecdh.go - Adapts ecdh module to our NIKE interface. 2 | // Copyright (C) 2022 David Stainton and Yawning Angel 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package x25519 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | "golang.org/x/crypto/curve25519" 25 | 26 | "github.com/katzenpost/hpqc/rand" 27 | "github.com/katzenpost/hpqc/util" 28 | ) 29 | 30 | func TestEcdhNike(t *testing.T) { 31 | ecdhNike := Scheme(rand.Reader) 32 | 33 | alicePublicKey, alicePrivateKey, err := ecdhNike.GenerateKeyPair() 34 | require.NoError(t, err) 35 | 36 | tmp := alicePrivateKey.(*PrivateKey).Public() 37 | require.Equal(t, alicePublicKey.Bytes(), tmp.Bytes()) 38 | 39 | bobKeypair, err := NewKeypair(rand.Reader) 40 | require.NoError(t, err) 41 | 42 | aliceS := ecdhNike.DeriveSecret(alicePrivateKey, bobKeypair.Public()) 43 | 44 | bobS := Exp(alicePublicKey.Bytes(), bobKeypair.Bytes()) 45 | require.Equal(t, bobS, aliceS) 46 | } 47 | 48 | func TestPrivateKey(t *testing.T) { 49 | t.Parallel() 50 | assert := assert.New(t) 51 | 52 | var shortBuffer = []byte("Short Buffer") 53 | 54 | privKey, err := NewKeypair(rand.Reader) 55 | require.NoError(t, err, "NewKeypair failed") 56 | 57 | var privKey2 PrivateKey 58 | assert.Error(privKey2.FromBytes(shortBuffer), "PrivateKey.FromBytes(short)") 59 | 60 | err = privKey2.FromBytes(privKey.Bytes()) 61 | assert.NoError(err, "PrivateKey.ToBytes()->FromBytes()") 62 | assert.Equal(privKey, &privKey2, "PrivateKey.ToBytes()->FromBytes()") 63 | 64 | privKey2.Reset() 65 | assert.True(util.CtIsZero(privKey2.Bytes()), "PrivateKey.Reset()") 66 | 67 | var pubKey PublicKey 68 | assert.Error(pubKey.FromBytes(shortBuffer), "PublicKey.FromBytes(short)") 69 | 70 | err = pubKey.FromBytes(privKey.Public().Bytes()) 71 | assert.NoError(err, "PrivateKey.PublicKey().Bytes->FromBytes()") 72 | assert.Equal(privKey.Public().Bytes(), pubKey.Bytes(), "PrivateKey.PublicKey().Bytes->FromBytes()") 73 | } 74 | 75 | func TestECDHOps(t *testing.T) { 76 | t.Parallel() 77 | assert := assert.New(t) 78 | 79 | aliceKeypair, err := NewKeypair(rand.Reader) 80 | require.NoError(t, err, "NewKeygen() Alice failed") 81 | 82 | var bobSk, bobPk, bobS, tmp [GroupElementLength]byte 83 | _, err = rand.Reader.Read(bobSk[:]) 84 | require.NoError(t, err, "failed to generate bobSk") 85 | curve25519.ScalarBaseMult(&bobPk, &bobSk) 86 | 87 | curve25519.ScalarBaseMult(&tmp, (*[GroupElementLength]byte)(aliceKeypair)) 88 | assert.Equal(aliceKeypair.Public().Bytes(), tmp[:], "ExpG() mismatch against X25519 scalar base mult") 89 | 90 | aliceS := Exp(bobPk[:], aliceKeypair[:]) 91 | copy(tmp[:], aliceKeypair.Public().Bytes()) 92 | curve25519.ScalarMult(&bobS, &bobSk, &tmp) 93 | assert.Equal(bobS[:], aliceS, "Exp() mismatch against X25519 scalar mult") 94 | } 95 | -------------------------------------------------------------------------------- /kem/util/split_prf.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2023 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package util 5 | 6 | import ( 7 | "github.com/go-faster/xor" 8 | "golang.org/x/crypto/blake2b" 9 | ) 10 | 11 | // SplitPRF can be used with any number of KEMs 12 | // and it implement split PRF KEM combiner as: 13 | // 14 | // cct := cct1 || cct2 || cct3 || ... 15 | // return H(ss1 || cct) XOR H(ss2 || cct) XOR H(ss3 || cct) 16 | // 17 | // in order to retain IND-CCA2 security 18 | // as described in KEM Combiners https://eprint.iacr.org/2018/024.pdf 19 | // by Federico Giacon, Felix Heuer, and Bertram Poettering 20 | func SplitPRF(ss, cct [][]byte) []byte { 21 | 22 | if len(ss) != len(cct) { 23 | panic("mismatched slices") 24 | } 25 | 26 | cctcat := []byte{} 27 | for i := 0; i < len(cct); i++ { 28 | if cct[i] == nil { 29 | panic("ciphertext cannot be nil") 30 | } 31 | if len(cct[i]) == 0 { 32 | panic("ciphertext cannot be zero length") 33 | } 34 | cctcat = append(cctcat, cct[i]...) 35 | } 36 | 37 | hashes := make([][]byte, len(ss)) 38 | for i := 0; i < len(ss); i++ { 39 | h, err := blake2b.New256(nil) 40 | if err != nil { 41 | panic(err) 42 | } 43 | if ss[i] == nil { 44 | panic("shared secret cannot be nil") 45 | } 46 | if len(ss[i]) == 0 { 47 | panic("shared secret cannot be zero length") 48 | } 49 | _, err = h.Write(ss[i]) 50 | if err != nil { 51 | panic(err) 52 | } 53 | _, err = h.Write(cctcat) 54 | if err != nil { 55 | panic(err) 56 | } 57 | hashes[i] = h.Sum(nil) 58 | } 59 | 60 | acc := hashes[0] 61 | for i := 1; i < len(ss); i++ { 62 | out := make([]byte, 32) 63 | xor.Bytes(out, acc, hashes[i]) 64 | acc = out 65 | } 66 | return acc 67 | } 68 | 69 | // PairSplitPRF is a split PRF that operates on only two KEMs. 70 | func PairSplitPRF(ss1, ss2, cct1, cct2 []byte) []byte { 71 | return SplitPRF([][]byte{ss1, ss2}, [][]byte{cct1, cct2}) 72 | } 73 | 74 | // This is a simplified split PRF construction 75 | // that only works for combining two KEMs. If we 76 | // were to use this it would make our hybrid KEM combiner 77 | // NOT binary compatible with our multi KEM combiner when 78 | // it's combinging only two KEMs. 79 | // Keeping it here for posterity and just in case we only want 80 | // to combiner two KEMs we could just use this and get rid of 81 | // the other combiner. That's one possible future route to take. 82 | func nopePairSplitPRF(ss1, ss2, cct1, cct2 []byte) []byte { 83 | 84 | // implement split PRF KEM combiner as: 85 | // 86 | // func splitPRF(ss1, ss2, cct1, cct2 []byte) []byte { 87 | // cct := cct1 || cct2 88 | // return H(ss1 || cct) XOR H(ss2 || cct) 89 | // } 90 | // 91 | // Which simplifies to: 92 | // 93 | // splitPRF := PRF(ss1 || cct2) XOR PRF(ss2 || cct1) 94 | // 95 | // in order to retain IND-CCA2 security 96 | // as described in KEM Combiners 97 | // by Federico Giacon, Felix Heuer, and Bertram Poettering 98 | // https://eprint.iacr.org/2018/024.pdf 99 | // 100 | 101 | h1, err := blake2b.New256(nil) 102 | if err != nil { 103 | panic(err) 104 | } 105 | _, err = h1.Write(ss1) 106 | if err != nil { 107 | panic(err) 108 | } 109 | _, err = h1.Write(cct2) 110 | if err != nil { 111 | panic(err) 112 | } 113 | hash1 := h1.Sum(nil) 114 | 115 | h2, err := blake2b.New256(nil) 116 | if err != nil { 117 | panic(err) 118 | } 119 | _, err = h2.Write(ss2) 120 | if err != nil { 121 | panic(err) 122 | } 123 | _, err = h2.Write(cct1) 124 | if err != nil { 125 | panic(err) 126 | } 127 | hash2 := h2.Sum(nil) 128 | 129 | out := make([]byte, len(hash1)) 130 | xor.Bytes(out, hash1, hash2) 131 | return out 132 | } 133 | -------------------------------------------------------------------------------- /kem/mkem/mkem_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package mkem provides multiparty KEM construction. 5 | package mkem 6 | 7 | import ( 8 | "crypto/rand" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/katzenpost/hpqc/nike" 14 | "github.com/katzenpost/hpqc/nike/schemes" 15 | ) 16 | 17 | func TestCiphertextMarshaling(t *testing.T) { 18 | dek := [DEKSize]byte{} 19 | _, err := rand.Reader.Read(dek[:]) 20 | require.NoError(t, err) 21 | ic := &IntermediaryCiphertext{ 22 | EphemeralPublicKey: []byte("hello1"), 23 | DEKCiphertexts: []*[DEKSize]byte{&dek}, 24 | Envelope: []byte("hello i am ciphertext"), 25 | } 26 | blob1 := ic.Bytes() 27 | 28 | ic2 := &IntermediaryCiphertext{} 29 | err = ic2.FromBytes(blob1) 30 | require.NoError(t, err) 31 | blob2 := ic2.Bytes() 32 | require.Equal(t, blob1, blob2) 33 | 34 | ic3 := &IntermediaryCiphertext{} 35 | err = ic3.FromBytes(blob2) 36 | require.NoError(t, err) 37 | blob3 := ic3.Bytes() 38 | require.Equal(t, blob1, blob3) 39 | } 40 | 41 | func TestMKEMCorrectness(t *testing.T) { 42 | nikeScheme := schemes.ByName("CTIDH1024-X25519") 43 | s := NewScheme(nikeScheme) 44 | 45 | replica1pub, replica1priv, err := s.GenerateKeyPair() 46 | require.NoError(t, err) 47 | 48 | replica2pub, replica2priv, err := s.GenerateKeyPair() 49 | require.NoError(t, err) 50 | 51 | secret := make([]byte, 32) 52 | _, err = rand.Reader.Read(secret) 53 | require.NoError(t, err) 54 | 55 | _, ciphertext := s.Encapsulate([]nike.PublicKey{replica1pub, replica2pub}, secret) 56 | 57 | secret1, err := s.Decapsulate(replica1priv, ciphertext) 58 | require.NoError(t, err) 59 | 60 | require.Equal(t, secret, secret1) 61 | 62 | secret2, err := s.Decapsulate(replica2priv, ciphertext) 63 | require.NoError(t, err) 64 | 65 | require.Equal(t, secret, secret2) 66 | } 67 | 68 | func TestMKEMProtocol(t *testing.T) { 69 | nikeScheme := schemes.ByName("CTIDH1024-X25519") 70 | s := NewScheme(nikeScheme) 71 | 72 | // replicas create their keys and publish them 73 | replica0pub, replica0priv, err := s.GenerateKeyPair() 74 | require.NoError(t, err) 75 | replica1pub, replica1priv, err := s.GenerateKeyPair() 76 | require.NoError(t, err) 77 | 78 | // client to replica 79 | request := make([]byte, 31) 80 | _, err = rand.Reader.Read(request) 81 | require.NoError(t, err) 82 | privKey0, envelope := s.Encapsulate([]nike.PublicKey{replica0pub, replica1pub}, request) 83 | 84 | ct0 := &Ciphertext{ 85 | EphemeralPublicKey: envelope.EphemeralPublicKey, 86 | DEKCiphertexts: []*[DEKSize]byte{envelope.DEKCiphertexts[0]}, 87 | Envelope: envelope.Envelope, 88 | } 89 | 90 | ct1 := &Ciphertext{ 91 | EphemeralPublicKey: envelope.EphemeralPublicKey, 92 | DEKCiphertexts: []*[DEKSize]byte{envelope.DEKCiphertexts[1]}, 93 | Envelope: envelope.Envelope, 94 | } 95 | 96 | // replica0 decrypts message from client 97 | request0, err := s.Decapsulate(replica0priv, ct0) 98 | require.NoError(t, err) 99 | require.Equal(t, request0, request) 100 | // XXX require.Equal(t, len(ct0.DEKCiphertexts[0]), DEKSize) 101 | 102 | // replica1 decrypts message from client 103 | request1, err := s.Decapsulate(replica1priv, ct1) 104 | require.NoError(t, err) 105 | require.Equal(t, request1, request) 106 | 107 | replyPayload := []byte("hello") 108 | reply0 := s.EnvelopeReply(replica0priv, envelope.EphemeralPublicKey, replyPayload) 109 | 110 | // client decrypts reply from replica 111 | plaintext, err := s.DecryptEnvelope(privKey0, replica0pub, reply0.Envelope) 112 | require.NoError(t, err) 113 | 114 | require.Equal(t, replyPayload, plaintext) 115 | } 116 | -------------------------------------------------------------------------------- /rand/rand_linux.go: -------------------------------------------------------------------------------- 1 | // rand_linux.go - Linux getentropy() based on getrandom(). 2 | // Copyright (C) 2016 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package rand 18 | 19 | import ( 20 | "bytes" 21 | "crypto/rand" 22 | "os" 23 | "runtime" 24 | "strconv" 25 | "syscall" 26 | "time" 27 | "unsafe" 28 | ) 29 | 30 | var getrandomTrap uintptr 31 | 32 | // Mimic OpenBSD's getentropy semantics. 33 | // 34 | // This means: 35 | // * BLOCK like god intended it, if the system entropy source isn't 36 | // initialized. 37 | // * Don't ever return truncated reads, even if signal handlers are involved. 38 | // * Reject reads over 256 bytes long. 39 | func getentropy(b []byte) error { 40 | if len(b) <= 256 { 41 | var buf, buflen, flags uintptr 42 | buf = uintptr(unsafe.Pointer(&b[0])) 43 | buflen = uintptr(len(b)) 44 | flags = 0 45 | 46 | r1, _, err := syscall.Syscall(getrandomTrap, buf, buflen, flags) 47 | if err != 0 { 48 | return err 49 | } 50 | if r1 == buflen { 51 | return nil 52 | } 53 | } 54 | 55 | return syscall.EIO 56 | } 57 | 58 | func waitOnUrandomSanity() error { 59 | for { 60 | // Use the /proc interface to query the entropy estimate. 61 | buf, err := os.ReadFile("/proc/sys/kernel/random/entropy_avail") 62 | if err != nil { 63 | return err 64 | } 65 | entropy, err := strconv.ParseInt(string(bytes.TrimSpace(buf)), 10, 0) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // The kernel considers an entropy pool initialized if it ever 71 | // exceeds 128 bits of entropy. Since we can't tell if this has 72 | // happened in the past for the nonblocking pool, wait till we 73 | // see the threshold has been exceeded. 74 | if entropy > 128 { 75 | return nil 76 | } 77 | 78 | // Don't busy wait. 79 | time.Sleep(1 * time.Second) 80 | } 81 | } 82 | 83 | // Detect support for getrandom(2). 84 | func initGetrandom() error { 85 | switch runtime.GOARCH { 86 | case "amd64": 87 | getrandomTrap = 318 88 | case "386": 89 | getrandomTrap = 355 90 | case "arm": 91 | getrandomTrap = 384 92 | case "arm64": 93 | getrandomTrap = 278 94 | default: 95 | // Your platform is the most special snowflake of them all. 96 | return syscall.ENOSYS 97 | } 98 | 99 | var err error 100 | var tmp [1]byte 101 | for { 102 | err = getentropy(tmp[:]) 103 | switch err { 104 | case nil: 105 | return nil 106 | case syscall.EINTR: 107 | // Interrupted by a signal handler while waiting for the entropy 108 | // pool to initialize, try again. 109 | default: 110 | return err 111 | } 112 | } 113 | } 114 | 115 | func init() { 116 | if err := initGetrandom(); err == nil { 117 | // getrandom(2) appears to work, and is initialized. 118 | usingImprovedSyscallEntropy = true 119 | Reader = &nonShitRandReader{getentropy} 120 | } else { 121 | // The system is likely older than Linux 3.17, which while 122 | // prehistoric, is still used on things. 123 | // 124 | // Wait till the system entropy pool is sufficiently initialized, 125 | // such that crypto/rand.Reader returns quality results. 126 | if err = waitOnUrandomSanity(); err != nil { 127 | panic("rand: failed to get a sane /dev/urandom: " + err.Error()) 128 | } 129 | Reader = rand.Reader 130 | } 131 | initWhitening() 132 | } 133 | -------------------------------------------------------------------------------- /kem/mkem/mkem.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package mkem provides multiparty KEM construction. 5 | package mkem 6 | 7 | import ( 8 | "crypto/cipher" 9 | "crypto/rand" 10 | "errors" 11 | 12 | "github.com/katzenpost/chacha20poly1305" 13 | "github.com/katzenpost/hpqc/hash" 14 | "github.com/katzenpost/hpqc/nike" 15 | ) 16 | 17 | // DEKSize indicates the size in bytes of the DEK 18 | // within our Ciphertext type in it's DEKCiphertexts field. 19 | const DEKSize = 60 20 | 21 | // Scheme is an MKEM scheme. 22 | type Scheme struct { 23 | nike nike.Scheme 24 | } 25 | 26 | func NewScheme(scheme nike.Scheme) *Scheme { 27 | return &Scheme{ 28 | nike: scheme, 29 | } 30 | } 31 | 32 | func (s *Scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 33 | pubkey, privkey, err := s.nike.GenerateKeyPair() 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | return pubkey, privkey, nil 38 | } 39 | 40 | func (s *Scheme) createCipher(key []byte) cipher.AEAD { 41 | aead, err := chacha20poly1305.New(key) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return aead 46 | } 47 | 48 | func (s *Scheme) encrypt(key []byte, plaintext []byte) []byte { 49 | aead := s.createCipher(key) 50 | nonce := make([]byte, aead.NonceSize()) 51 | _, err := rand.Reader.Read(nonce) 52 | if err != nil { 53 | panic(err) 54 | } 55 | return aead.Seal(nonce, nonce, plaintext, nil) 56 | } 57 | 58 | func (s *Scheme) decrypt(key []byte, ciphertext []byte) ([]byte, error) { 59 | aead := s.createCipher(key) 60 | nonce := ciphertext[:aead.NonceSize()] 61 | ciphertext = ciphertext[aead.NonceSize():] 62 | return aead.Open(nil, nonce, ciphertext, nil) 63 | } 64 | 65 | func (s *Scheme) EnvelopeReply(privkey nike.PrivateKey, pubkey nike.PublicKey, plaintext []byte) *Ciphertext { 66 | secret := hash.Sum256(s.nike.DeriveSecret(privkey, pubkey)) 67 | ciphertext := s.encrypt(secret[:], plaintext) 68 | c := &Ciphertext{ 69 | EphemeralPublicKey: pubkey, 70 | DEKCiphertexts: nil, 71 | Envelope: ciphertext, 72 | } 73 | return c 74 | } 75 | 76 | func (s *Scheme) DecryptEnvelope(privkey nike.PrivateKey, pubkey nike.PublicKey, envelope []byte) ([]byte, error) { 77 | secret := hash.Sum256(s.nike.DeriveSecret(privkey, pubkey)) 78 | plaintext, err := s.decrypt(secret[:], envelope) 79 | if err != nil { 80 | return nil, err 81 | } 82 | return plaintext, nil 83 | } 84 | 85 | func (s *Scheme) Encapsulate(keys []nike.PublicKey, payload []byte) (nike.PrivateKey, *Ciphertext) { 86 | ephPub, ephPriv, err := s.nike.GenerateKeyPair() 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | secrets := make([][hash.HashSize]byte, len(keys)) 92 | for i := 0; i < len(keys); i++ { 93 | secrets[i] = hash.Sum256(s.nike.DeriveSecret(ephPriv, keys[i])) 94 | } 95 | 96 | msgKey := make([]byte, 32) 97 | _, err = rand.Reader.Read(msgKey) 98 | if err != nil { 99 | panic(err) 100 | } 101 | ciphertext := s.encrypt(msgKey, payload) 102 | 103 | outCiphertexts := make([]*[DEKSize]byte, len(secrets)) 104 | for i := 0; i < len(secrets); i++ { 105 | outCiphertexts[i] = &[DEKSize]byte{} 106 | copy(outCiphertexts[i][:], s.encrypt(secrets[i][:], msgKey)) 107 | if len(outCiphertexts[i]) != DEKSize { 108 | panic("invalid ciphertext size") 109 | } 110 | } 111 | 112 | c := &Ciphertext{ 113 | EphemeralPublicKey: ephPub, 114 | DEKCiphertexts: outCiphertexts, 115 | Envelope: ciphertext, 116 | } 117 | return ephPriv, c 118 | } 119 | 120 | func (s *Scheme) Decapsulate(privkey nike.PrivateKey, ciphertext *Ciphertext) ([]byte, error) { 121 | ephSecret := hash.Sum256(s.nike.DeriveSecret(privkey, ciphertext.EphemeralPublicKey)) 122 | for i := 0; i < len(ciphertext.DEKCiphertexts); i++ { 123 | msgKey, err := s.decrypt(ephSecret[:], ciphertext.DEKCiphertexts[i][:]) 124 | if err != nil { 125 | continue 126 | } 127 | return s.decrypt(msgKey, ciphertext.Envelope) 128 | } 129 | return nil, errors.New("failed to trial decrypt") 130 | } 131 | -------------------------------------------------------------------------------- /kem/mlkem768/mlkem768.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package mlkem768 provides a KEM wrapper that uses our KEM interfaces. 5 | package mlkem768 6 | 7 | import ( 8 | "crypto/hmac" 9 | "errors" 10 | 11 | "filippo.io/mlkem768" 12 | 13 | "github.com/katzenpost/hpqc/kem" 14 | "github.com/katzenpost/hpqc/kem/pem" 15 | ) 16 | 17 | const ( 18 | SeedSize = 64 19 | SharedKeySize = mlkem768.SharedKeySize 20 | CiphertextSize = mlkem768.CiphertextSize 21 | PublicKeySize = mlkem768.EncapsulationKeySize 22 | PrivateKeySize = PublicKeySize + mlkem768.DecapsulationKeySize 23 | ) 24 | 25 | // tell the type checker that we obey these interfaces 26 | var _ kem.Scheme = (*scheme)(nil) 27 | var _ kem.PublicKey = (*PublicKey)(nil) 28 | var _ kem.PrivateKey = (*PrivateKey)(nil) 29 | 30 | var sch kem.Scheme = &scheme{} 31 | 32 | // Scheme returns a KEM interface. 33 | func Scheme() kem.Scheme { return sch } 34 | 35 | type PublicKey struct { 36 | scheme *scheme 37 | encapKey []byte 38 | } 39 | 40 | func (p *PublicKey) Scheme() kem.Scheme { 41 | return p.scheme 42 | } 43 | 44 | func (p *PublicKey) MarshalText() (text []byte, err error) { 45 | return pem.ToPublicPEMBytes(p), nil 46 | } 47 | 48 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 49 | return p.encapKey, nil 50 | } 51 | 52 | func (p *PublicKey) Equal(pubkey kem.PublicKey) bool { 53 | if pubkey.(*PublicKey).scheme != p.scheme { 54 | return false 55 | } 56 | return hmac.Equal(pubkey.(*PublicKey).encapKey, p.encapKey) 57 | } 58 | 59 | type PrivateKey struct { 60 | scheme *scheme 61 | decapKey []byte 62 | encapKey []byte 63 | } 64 | 65 | func (p *PrivateKey) Scheme() kem.Scheme { 66 | return p.scheme 67 | } 68 | 69 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 70 | return append(p.decapKey, p.encapKey...), nil 71 | } 72 | 73 | func (p *PrivateKey) Equal(privkey kem.PrivateKey) bool { 74 | if privkey.(*PrivateKey).scheme != p.scheme { 75 | return false 76 | } 77 | return hmac.Equal(privkey.(*PrivateKey).decapKey, p.decapKey) 78 | } 79 | 80 | func (p *PrivateKey) Public() kem.PublicKey { 81 | return &PublicKey{ 82 | encapKey: p.encapKey, 83 | scheme: p.scheme, 84 | } 85 | } 86 | 87 | type scheme struct { 88 | } 89 | 90 | func (s *scheme) Name() string { 91 | return "MLKEM768" 92 | } 93 | 94 | func (a *scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { 95 | encapKey, decapKey, err := mlkem768.GenerateKey() 96 | if err != nil { 97 | return nil, nil, err 98 | } 99 | return &PublicKey{ 100 | scheme: a, 101 | encapKey: encapKey, 102 | }, &PrivateKey{ 103 | scheme: a, 104 | encapKey: encapKey, 105 | decapKey: decapKey, 106 | }, nil 107 | } 108 | 109 | func (s *scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { 110 | return mlkem768.Encapsulate(pk.(*PublicKey).encapKey) 111 | } 112 | 113 | func (s *scheme) Decapsulate(myPrivkey kem.PrivateKey, ct []byte) ([]byte, error) { 114 | return mlkem768.Decapsulate(myPrivkey.(*PrivateKey).decapKey, ct) 115 | } 116 | 117 | func (s *scheme) UnmarshalBinaryPublicKey(b []byte) (kem.PublicKey, error) { 118 | if len(b) != PublicKeySize { 119 | return nil, errors.New("wrong key size") 120 | } 121 | return &PublicKey{ 122 | scheme: s, 123 | encapKey: b, 124 | }, nil 125 | } 126 | 127 | func (s *scheme) UnmarshalBinaryPrivateKey(b []byte) (kem.PrivateKey, error) { 128 | if len(b) != PrivateKeySize { 129 | return nil, errors.New("wrong key size") 130 | } 131 | return &PrivateKey{ 132 | scheme: s, 133 | decapKey: b[:mlkem768.DecapsulationKeySize], 134 | encapKey: b[mlkem768.DecapsulationKeySize:], 135 | }, nil 136 | } 137 | 138 | func (s *scheme) UnmarshalTextPublicKey(text []byte) (kem.PublicKey, error) { 139 | return pem.FromPublicPEMBytes(text, s) 140 | } 141 | 142 | func (s *scheme) UnmarshalTextPrivateKey(text []byte) (kem.PrivateKey, error) { 143 | return pem.FromPrivatePEMBytes(text, s) 144 | } 145 | 146 | func (s *scheme) CiphertextSize() int { 147 | return CiphertextSize 148 | } 149 | 150 | func (s *scheme) SharedKeySize() int { 151 | return SharedKeySize 152 | } 153 | 154 | func (s *scheme) PrivateKeySize() int { 155 | return PrivateKeySize 156 | } 157 | 158 | func (s *scheme) PublicKeySize() int { 159 | return PublicKeySize 160 | } 161 | 162 | func (s *scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { 163 | if len(seed) != SeedSize { 164 | panic(kem.ErrSeedSize) 165 | } 166 | encapKey, decapKey, err := mlkem768.NewKeyFromSeed(seed) 167 | if err != nil { 168 | panic(err) 169 | } 170 | return &PublicKey{ 171 | scheme: s, 172 | encapKey: encapKey, 173 | }, &PrivateKey{ 174 | scheme: s, 175 | encapKey: encapKey, 176 | decapKey: decapKey, 177 | } 178 | } 179 | 180 | func (s *scheme) SeedSize() int { 181 | return SeedSize 182 | } 183 | -------------------------------------------------------------------------------- /kem/pem/pem.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2023-2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package pem 5 | 6 | import ( 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "os" 11 | "strings" 12 | 13 | "github.com/katzenpost/hpqc/kem" 14 | "github.com/katzenpost/hpqc/util" 15 | ) 16 | 17 | func ToPublicPEMString(key kem.PublicKey) string { 18 | return string(ToPublicPEMBytes(key)) 19 | } 20 | 21 | func ToPublicPEMBytes(key kem.PublicKey) []byte { 22 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(key.Scheme().Name())) 23 | blob, err := key.MarshalBinary() 24 | if err != nil { 25 | panic(err) 26 | } 27 | if util.CtIsZero(blob) { 28 | panic(fmt.Sprintf("ToPEMString/%s: attempted to serialize scrubbed key", keyType)) 29 | } 30 | blk := &pem.Block{ 31 | Type: keyType, 32 | Bytes: blob, 33 | } 34 | return pem.EncodeToMemory(blk) 35 | } 36 | 37 | func PublicKeyToFile(f string, key kem.PublicKey) error { 38 | out, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 39 | if err != nil { 40 | return err 41 | } 42 | outBuf := ToPublicPEMBytes(key) 43 | writeCount, err := out.Write(outBuf) 44 | if err != nil { 45 | return err 46 | } 47 | if writeCount != len(outBuf) { 48 | return errors.New("partial write failure") 49 | } 50 | err = out.Sync() 51 | if err != nil { 52 | return err 53 | } 54 | return out.Close() 55 | } 56 | 57 | func FromPublicPEMString(s string, scheme kem.Scheme) (kem.PublicKey, error) { 58 | return FromPublicPEMBytes([]byte(s), scheme) 59 | } 60 | 61 | func FromPublicPEMBytes(b []byte, scheme kem.Scheme) (kem.PublicKey, error) { 62 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name())) 63 | blk, _ := pem.Decode(b) 64 | if blk == nil { 65 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 66 | } 67 | if strings.ToUpper(blk.Type) != keyType { 68 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 69 | } 70 | return scheme.UnmarshalBinaryPublicKey(blk.Bytes) 71 | } 72 | 73 | func FromPublicPEMToBytes(b []byte, scheme kem.Scheme) ([]byte, error) { 74 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name())) 75 | blk, _ := pem.Decode(b) 76 | if blk == nil { 77 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 78 | } 79 | if strings.ToUpper(blk.Type) != keyType { 80 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 81 | } 82 | return blk.Bytes, nil 83 | } 84 | 85 | func FromPublicPEMFile(f string, scheme kem.Scheme) (kem.PublicKey, error) { 86 | buf, err := os.ReadFile(f) 87 | if err != nil { 88 | return nil, fmt.Errorf("pem.FromFile error: %s", err) 89 | } 90 | return FromPublicPEMBytes(buf, scheme) 91 | } 92 | 93 | // private key 94 | 95 | func ToPrivatePEMString(key kem.PrivateKey) string { 96 | return string(ToPrivatePEMBytes(key)) 97 | } 98 | 99 | func ToPrivatePEMBytes(key kem.PrivateKey) []byte { 100 | keyType := fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(key.Scheme().Name())) 101 | blob, err := key.MarshalBinary() 102 | if err != nil { 103 | panic(err) 104 | } 105 | if util.CtIsZero(blob) { 106 | panic(fmt.Sprintf("ToPEMString/%s: attempted to serialize scrubbed key", keyType)) 107 | } 108 | blk := &pem.Block{ 109 | Type: keyType, 110 | Bytes: blob, 111 | } 112 | return pem.EncodeToMemory(blk) 113 | } 114 | 115 | func PrivateKeyToFile(f string, key kem.PrivateKey) error { 116 | out, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 117 | if err != nil { 118 | return err 119 | } 120 | outBuf := ToPrivatePEMBytes(key) 121 | writeCount, err := out.Write(outBuf) 122 | if err != nil { 123 | return err 124 | } 125 | if writeCount != len(outBuf) { 126 | return errors.New("partial write failure") 127 | } 128 | err = out.Sync() 129 | if err != nil { 130 | return err 131 | } 132 | return out.Close() 133 | } 134 | 135 | func FromPrivatePEMString(s string, scheme kem.Scheme) (kem.PrivateKey, error) { 136 | return FromPrivatePEMBytes([]byte(s), scheme) 137 | } 138 | 139 | func FromPrivatePEMBytes(b []byte, scheme kem.Scheme) (kem.PrivateKey, error) { 140 | keyType := fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(scheme.Name())) 141 | blk, _ := pem.Decode(b) 142 | if blk == nil { 143 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 144 | } 145 | if strings.ToUpper(blk.Type) != keyType { 146 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 147 | } 148 | return scheme.UnmarshalBinaryPrivateKey(blk.Bytes) 149 | } 150 | 151 | func FromPrivatePEMFile(f string, scheme kem.Scheme) (kem.PrivateKey, error) { 152 | buf, err := os.ReadFile(f) 153 | if err != nil { 154 | return nil, fmt.Errorf("pem.FromFile error: %s", err) 155 | } 156 | return FromPrivatePEMBytes(buf, scheme) 157 | } 158 | -------------------------------------------------------------------------------- /kem/xwing/xwing.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package xwing provides the xwing KEM using a KEM wrapper 5 | // so that it obeys our KEM interfaces for Scheme, PrivateKey, PublicKey. 6 | package xwing 7 | 8 | import ( 9 | "crypto/hmac" 10 | "errors" 11 | 12 | "filippo.io/mlkem768/xwing" 13 | 14 | "github.com/katzenpost/hpqc/kem" 15 | "github.com/katzenpost/hpqc/kem/pem" 16 | ) 17 | 18 | const ( 19 | KeySeedSize = xwing.SeedSize 20 | SharedKeySize = xwing.SharedKeySize 21 | CiphertextSize = xwing.CiphertextSize 22 | PublicKeySize = xwing.EncapsulationKeySize 23 | PrivateKeySize = PublicKeySize + xwing.DecapsulationKeySize 24 | ) 25 | 26 | // tell the type checker that we obey these interfaces 27 | var _ kem.Scheme = (*scheme)(nil) 28 | var _ kem.PublicKey = (*PublicKey)(nil) 29 | var _ kem.PrivateKey = (*PrivateKey)(nil) 30 | 31 | var sch kem.Scheme = &scheme{} 32 | 33 | // Scheme returns a KEM interface. 34 | func Scheme() kem.Scheme { return sch } 35 | 36 | type PublicKey struct { 37 | scheme *scheme 38 | encapKey []byte 39 | } 40 | 41 | func (p *PublicKey) Scheme() kem.Scheme { 42 | return p.scheme 43 | } 44 | 45 | func (p *PublicKey) MarshalText() (text []byte, err error) { 46 | return pem.ToPublicPEMBytes(p), nil 47 | } 48 | 49 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 50 | return p.encapKey, nil 51 | } 52 | 53 | func (p *PublicKey) Equal(pubkey kem.PublicKey) bool { 54 | if pubkey.(*PublicKey).scheme != p.scheme { 55 | return false 56 | } 57 | return hmac.Equal(pubkey.(*PublicKey).encapKey, p.encapKey) 58 | } 59 | 60 | type PrivateKey struct { 61 | scheme *scheme 62 | decapKey []byte 63 | encapKey []byte 64 | } 65 | 66 | func (p *PrivateKey) Scheme() kem.Scheme { 67 | return p.scheme 68 | } 69 | 70 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 71 | return append(p.decapKey, p.encapKey...), nil 72 | } 73 | 74 | func (p *PrivateKey) Equal(privkey kem.PrivateKey) bool { 75 | if privkey.(*PrivateKey).scheme != p.scheme { 76 | return false 77 | } 78 | return hmac.Equal(privkey.(*PrivateKey).decapKey, p.decapKey) 79 | } 80 | 81 | func (p *PrivateKey) Public() kem.PublicKey { 82 | return &PublicKey{ 83 | encapKey: p.encapKey, 84 | scheme: p.scheme, 85 | } 86 | } 87 | 88 | type scheme struct { 89 | } 90 | 91 | func (s *scheme) Name() string { 92 | return "XWING" 93 | } 94 | 95 | func (a *scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { 96 | encapKey, decapKey, err := xwing.GenerateKey() 97 | if err != nil { 98 | return nil, nil, err 99 | } 100 | return &PublicKey{ 101 | scheme: a, 102 | encapKey: encapKey, 103 | }, &PrivateKey{ 104 | scheme: a, 105 | encapKey: encapKey, 106 | decapKey: decapKey, 107 | }, nil 108 | } 109 | 110 | func (s *scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { 111 | return xwing.Encapsulate(pk.(*PublicKey).encapKey) 112 | } 113 | 114 | func (s *scheme) Decapsulate(myPrivkey kem.PrivateKey, ct []byte) ([]byte, error) { 115 | return xwing.Decapsulate(myPrivkey.(*PrivateKey).decapKey, ct) 116 | } 117 | 118 | func (s *scheme) UnmarshalBinaryPublicKey(b []byte) (kem.PublicKey, error) { 119 | if len(b) != PublicKeySize { 120 | return nil, errors.New("wrong key size") 121 | } 122 | return &PublicKey{ 123 | scheme: s, 124 | encapKey: b, 125 | }, nil 126 | } 127 | 128 | func (s *scheme) UnmarshalBinaryPrivateKey(b []byte) (kem.PrivateKey, error) { 129 | if len(b) != PrivateKeySize { 130 | return nil, errors.New("wrong key size") 131 | } 132 | return &PrivateKey{ 133 | scheme: s, 134 | decapKey: b[:xwing.DecapsulationKeySize], 135 | encapKey: b[xwing.DecapsulationKeySize:], 136 | }, nil 137 | } 138 | 139 | func (s *scheme) UnmarshalTextPublicKey(text []byte) (kem.PublicKey, error) { 140 | return pem.FromPublicPEMBytes(text, s) 141 | } 142 | 143 | func (s *scheme) UnmarshalTextPrivateKey(text []byte) (kem.PrivateKey, error) { 144 | return pem.FromPrivatePEMBytes(text, s) 145 | } 146 | 147 | func (s *scheme) CiphertextSize() int { 148 | return CiphertextSize 149 | } 150 | 151 | func (s *scheme) SharedKeySize() int { 152 | return SharedKeySize 153 | } 154 | 155 | func (s *scheme) PrivateKeySize() int { 156 | return PrivateKeySize 157 | } 158 | 159 | func (s *scheme) PublicKeySize() int { 160 | return PublicKeySize 161 | } 162 | 163 | func (s *scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { 164 | if len(seed) != KeySeedSize { 165 | panic(kem.ErrSeedSize) 166 | } 167 | encapKey, decapKey, err := xwing.NewKeyFromSeed(seed) 168 | if err != nil { 169 | panic(err) 170 | } 171 | return &PublicKey{ 172 | scheme: s, 173 | encapKey: encapKey, 174 | }, &PrivateKey{ 175 | scheme: s, 176 | encapKey: encapKey, 177 | decapKey: decapKey, 178 | } 179 | } 180 | 181 | func (s *scheme) SeedSize() int { 182 | return KeySeedSize 183 | } 184 | -------------------------------------------------------------------------------- /sign/pem/pem.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2023-2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package pem 5 | 6 | import ( 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "os" 11 | "strings" 12 | 13 | "github.com/katzenpost/hpqc/sign" 14 | "github.com/katzenpost/hpqc/util" 15 | ) 16 | 17 | func ToPublicPEMString(key sign.PublicKey) string { 18 | return string(ToPublicPEMBytes(key)) 19 | } 20 | 21 | func ToPublicPEMBytes(key sign.PublicKey) []byte { 22 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(key.Scheme().Name())) 23 | blob, err := key.MarshalBinary() 24 | if err != nil { 25 | panic(err) 26 | } 27 | if util.CtIsZero(blob) { 28 | panic(fmt.Sprintf("ToPEMString/%s: attempted to serialize scrubbed key", keyType)) 29 | } 30 | blk := &pem.Block{ 31 | Type: keyType, 32 | Bytes: blob, 33 | } 34 | return pem.EncodeToMemory(blk) 35 | } 36 | 37 | func PublicKeyToFile(f string, key sign.PublicKey) error { 38 | out, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 39 | if err != nil { 40 | return err 41 | } 42 | outBuf := ToPublicPEMBytes(key) 43 | writeCount, err := out.Write(outBuf) 44 | if err != nil { 45 | return err 46 | } 47 | if writeCount != len(outBuf) { 48 | return errors.New("partial write failure") 49 | } 50 | err = out.Sync() 51 | if err != nil { 52 | return err 53 | } 54 | return out.Close() 55 | } 56 | 57 | func FromPublicPEMString(s string, scheme sign.Scheme) (sign.PublicKey, error) { 58 | return FromPublicPEMBytes([]byte(s), scheme) 59 | } 60 | 61 | func FromPublicPEMBytes(b []byte, scheme sign.Scheme) (sign.PublicKey, error) { 62 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name())) 63 | blk, _ := pem.Decode(b) 64 | if blk == nil { 65 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 66 | } 67 | if strings.ToUpper(blk.Type) != keyType { 68 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 69 | } 70 | return scheme.UnmarshalBinaryPublicKey(blk.Bytes) 71 | } 72 | 73 | func FromPublicPEMToBytes(b []byte, scheme sign.Scheme) ([]byte, error) { 74 | keyType := fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name())) 75 | blk, _ := pem.Decode(b) 76 | if blk == nil { 77 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 78 | } 79 | if strings.ToUpper(blk.Type) != keyType { 80 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 81 | } 82 | return blk.Bytes, nil 83 | } 84 | 85 | func FromPublicPEMFile(f string, scheme sign.Scheme) (sign.PublicKey, error) { 86 | buf, err := os.ReadFile(f) 87 | if err != nil { 88 | return nil, fmt.Errorf("pem.FromFile error: %s", err) 89 | } 90 | return FromPublicPEMBytes(buf, scheme) 91 | } 92 | 93 | // private key 94 | 95 | func ToPrivatePEMString(key sign.PrivateKey) string { 96 | return string(ToPrivatePEMBytes(key)) 97 | } 98 | 99 | func ToPrivatePEMBytes(key sign.PrivateKey) []byte { 100 | keyType := fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(key.Scheme().Name())) 101 | blob, err := key.MarshalBinary() 102 | if err != nil { 103 | panic(err) 104 | } 105 | if util.CtIsZero(blob) { 106 | panic(fmt.Sprintf("ToPEMString/%s: attempted to serialize scrubbed key", keyType)) 107 | } 108 | blk := &pem.Block{ 109 | Type: keyType, 110 | Bytes: blob, 111 | } 112 | return pem.EncodeToMemory(blk) 113 | } 114 | 115 | func PrivateKeyToFile(f string, key sign.PrivateKey) error { 116 | out, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 117 | if err != nil { 118 | return err 119 | } 120 | outBuf := ToPrivatePEMBytes(key) 121 | writeCount, err := out.Write(outBuf) 122 | if err != nil { 123 | return err 124 | } 125 | if writeCount != len(outBuf) { 126 | return errors.New("partial write failure") 127 | } 128 | err = out.Sync() 129 | if err != nil { 130 | return err 131 | } 132 | return out.Close() 133 | } 134 | 135 | func FromPrivatePEMString(s string, scheme sign.Scheme) (sign.PrivateKey, error) { 136 | return FromPrivatePEMBytes([]byte(s), scheme) 137 | } 138 | 139 | func FromPrivatePEMBytes(b []byte, scheme sign.Scheme) (sign.PrivateKey, error) { 140 | keyType := fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(scheme.Name())) 141 | blk, _ := pem.Decode(b) 142 | if blk == nil { 143 | return nil, fmt.Errorf("failed to decode PEM data from %s PEM", keyType) 144 | } 145 | if strings.ToUpper(blk.Type) != keyType { 146 | return nil, fmt.Errorf("attempted to decode PEM file with wrong key type %v != %v", blk.Type, keyType) 147 | } 148 | return scheme.UnmarshalBinaryPrivateKey(blk.Bytes) 149 | } 150 | 151 | func FromPrivatePEMFile(f string, scheme sign.Scheme) (sign.PrivateKey, error) { 152 | buf, err := os.ReadFile(f) 153 | if err != nil { 154 | return nil, fmt.Errorf("pem.FromFile error: %s", err) 155 | } 156 | return FromPrivatePEMBytes(buf, scheme) 157 | } 158 | -------------------------------------------------------------------------------- /nike/pem/pem.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2023-2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package pem 5 | 6 | import ( 7 | "encoding/pem" 8 | "fmt" 9 | "os" 10 | "strings" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | "github.com/katzenpost/hpqc/util" 14 | ) 15 | 16 | // ToPublicPEMString converts a public key to its PEM-encoded string representation. 17 | func ToPublicPEMString(key nike.PublicKey, scheme nike.Scheme) string { 18 | return string(ToPublicPEMBytes(key, scheme)) 19 | } 20 | 21 | // ToPublicPEMBytes converts a public key to its PEM-encoded byte slice. 22 | func ToPublicPEMBytes(key nike.PublicKey, scheme nike.Scheme) []byte { 23 | return toPEMBytes(key, fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name()))) 24 | } 25 | 26 | // PublicKeyToFile writes a public key to a file in PEM format. 27 | func PublicKeyToFile(filename string, key nike.PublicKey, scheme nike.Scheme) error { 28 | return writePEMToFile(filename, ToPublicPEMBytes(key, scheme)) 29 | } 30 | 31 | // FromPublicPEMString parses a PEM-encoded public key string. 32 | func FromPublicPEMString(data string, scheme nike.Scheme) (nike.PublicKey, error) { 33 | return FromPublicPEMBytes([]byte(data), scheme) 34 | } 35 | 36 | // FromPublicPEMBytes parses a PEM-encoded public key byte slice. 37 | func FromPublicPEMBytes(data []byte, scheme nike.Scheme) (nike.PublicKey, error) { 38 | blk, err := decodePEMBlock(data, fmt.Sprintf("%s PUBLIC KEY", strings.ToUpper(scheme.Name()))) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return scheme.UnmarshalBinaryPublicKey(blk.Bytes) 43 | } 44 | 45 | // FromPublicPEMFile reads and parses a public key from a PEM-encoded file. 46 | func FromPublicPEMFile(filename string, scheme nike.Scheme) (nike.PublicKey, error) { 47 | data, err := os.ReadFile(filename) 48 | if err != nil { 49 | return nil, fmt.Errorf("failed to read PEM file: %w", err) 50 | } 51 | return FromPublicPEMBytes(data, scheme) 52 | } 53 | 54 | // ToPrivatePEMString converts a private key to its PEM-encoded string representation. 55 | func ToPrivatePEMString(key nike.PrivateKey, scheme nike.Scheme) string { 56 | return string(ToPrivatePEMBytes(key, scheme)) 57 | } 58 | 59 | // ToPrivatePEMBytes converts a private key to its PEM-encoded byte slice. 60 | func ToPrivatePEMBytes(key nike.PrivateKey, scheme nike.Scheme) []byte { 61 | return toPEMBytes(key, fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(scheme.Name()))) 62 | } 63 | 64 | // PrivateKeyToFile writes a private key to a file in PEM format. 65 | func PrivateKeyToFile(filename string, key nike.PrivateKey, scheme nike.Scheme) error { 66 | return writePEMToFile(filename, ToPrivatePEMBytes(key, scheme)) 67 | } 68 | 69 | // FromPrivatePEMString parses a PEM-encoded private key string. 70 | func FromPrivatePEMString(data string, scheme nike.Scheme) (nike.PrivateKey, error) { 71 | return FromPrivatePEMBytes([]byte(data), scheme) 72 | } 73 | 74 | // FromPrivatePEMBytes parses a PEM-encoded private key byte slice. 75 | func FromPrivatePEMBytes(data []byte, scheme nike.Scheme) (nike.PrivateKey, error) { 76 | blk, err := decodePEMBlock(data, fmt.Sprintf("%s PRIVATE KEY", strings.ToUpper(scheme.Name()))) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return scheme.UnmarshalBinaryPrivateKey(blk.Bytes) 81 | } 82 | 83 | // FromPrivatePEMFile reads and parses a private key from a PEM-encoded file. 84 | func FromPrivatePEMFile(filename string, scheme nike.Scheme) (nike.PrivateKey, error) { 85 | data, err := os.ReadFile(filename) 86 | if err != nil { 87 | return nil, fmt.Errorf("failed to read PEM file: %w", err) 88 | } 89 | return FromPrivatePEMBytes(data, scheme) 90 | } 91 | 92 | // Helper Functions 93 | 94 | // toPEMBytes serializes a key to a PEM-encoded byte slice. 95 | func toPEMBytes(key nike.Key, keyType string) []byte { 96 | blob, err := key.MarshalBinary() 97 | if err != nil { 98 | panic(fmt.Sprintf("failed to marshal key: %v", err)) 99 | } 100 | if util.CtIsZero(blob) { 101 | panic(fmt.Sprintf("attempted to serialize a scrubbed key: %s", keyType)) 102 | } 103 | return pem.EncodeToMemory(&pem.Block{ 104 | Type: keyType, 105 | Bytes: blob, 106 | }) 107 | } 108 | 109 | // decodePEMBlock decodes a PEM block and verifies its type. 110 | func decodePEMBlock(data []byte, expectedType string) (*pem.Block, error) { 111 | blk, _ := pem.Decode(data) 112 | if blk == nil { 113 | return nil, fmt.Errorf("failed to decode PEM block of type %s", expectedType) 114 | } 115 | if strings.ToUpper(blk.Type) != expectedType { 116 | return nil, fmt.Errorf("PEM block type mismatch: got %s, want %s", blk.Type, expectedType) 117 | } 118 | return blk, nil 119 | } 120 | 121 | // writePEMToFile writes PEM-encoded data to a file securely. 122 | func writePEMToFile(filename string, pemData []byte) error { 123 | file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 124 | if err != nil { 125 | return fmt.Errorf("failed to open file for writing: %w", err) 126 | } 127 | defer file.Close() 128 | 129 | if _, err := file.Write(pemData); err != nil { 130 | return fmt.Errorf("failed to write to file: %w", err) 131 | } 132 | if err := file.Sync(); err != nil { 133 | return fmt.Errorf("failed to sync file: %w", err) 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /rand/math.go: -------------------------------------------------------------------------------- 1 | // math.go - math/rand replacement. 2 | // Copyright (C) 2017 Yawning Angel. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as 6 | // published by the Free Software Foundation, either version 3 of the 7 | // License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | // Package rand provides various utitilies related to generating 18 | // cryptographically secure random numbers and byte vectors. 19 | package rand 20 | 21 | import ( 22 | "encoding/binary" 23 | "io" 24 | "math" 25 | "math/rand" 26 | "sync" 27 | 28 | "github.com/katzenpost/chacha20" 29 | "github.com/katzenpost/hpqc/util" 30 | ) 31 | 32 | const seedSize = chacha20.KeySize 33 | 34 | var mNonce [chacha20.NonceSize]byte 35 | 36 | type randSource struct { 37 | sync.Mutex 38 | s *chacha20.Cipher 39 | off int 40 | } 41 | 42 | func (s *randSource) feedForward() { 43 | var seed [chacha20.KeySize]byte 44 | defer util.ExplicitBzero(seed[:]) 45 | s.s.KeyStream(seed[:]) 46 | if s.s.ReKey(seed[:], mNonce[:]) != nil { 47 | panic("chacha20 ReKey failed, not expected.") 48 | } 49 | s.off = 0 50 | } 51 | 52 | func (s *randSource) Uint64() uint64 { 53 | s.Lock() 54 | defer s.Unlock() 55 | 56 | if s.off+8 > chacha20.BlockSize-seedSize { 57 | s.feedForward() 58 | } 59 | 60 | s.off += 8 61 | 62 | var tmp [8]byte 63 | s.s.KeyStream(tmp[:]) 64 | return binary.LittleEndian.Uint64(tmp[:]) 65 | } 66 | 67 | func (s *randSource) Int63() int64 { 68 | ret := s.Uint64() 69 | return int64(ret & ((1 << 63) - 1)) 70 | } 71 | 72 | func (s *randSource) Seed(unused int64) { 73 | var seed [chacha20.KeySize]byte 74 | defer util.ExplicitBzero(seed[:]) 75 | if _, err := io.ReadFull(Reader, seed[:]); err != nil { 76 | panic("crypto/rand: failed to read entropy: " + err.Error()) 77 | } 78 | if err := s.s.ReKey(seed[:], mNonce[:]); err != nil { 79 | panic("s.s.ReKey(chacha20) failed, not expected") 80 | } 81 | s.off = 0 82 | } 83 | 84 | // NewMath returns a "cryptographically secure" math/rand.Rand. 85 | func NewMath() *rand.Rand { 86 | s := new(randSource) 87 | s.s = new(chacha20.Cipher) 88 | s.Seed(0) 89 | return rand.New(s) 90 | } 91 | 92 | // Exp returns a random sample from the exponential distribution characterized 93 | // by lambda (inverse of the mean). 94 | func Exp(r *rand.Rand, lambda float64) float64 { 95 | if lambda < math.SmallestNonzeroFloat64 { 96 | panic("crypto/rand: lambda out of range") 97 | } 98 | 99 | return r.ExpFloat64() / lambda 100 | } 101 | 102 | // ExpQuantile returns the value at which the the probability of a random value 103 | // is less than or equal to the given probability for an exponential 104 | // distribution characterized by lambda. 105 | func ExpQuantile(lambda, p float64) float64 { 106 | if lambda < math.SmallestNonzeroFloat64 { 107 | panic("crypto/rand: lambda out of range") 108 | } 109 | if p < math.SmallestNonzeroFloat64 || p >= 1.0 { 110 | panic("crypto/rand: p out of range") 111 | } 112 | 113 | return -math.Log(1-p) / lambda 114 | } 115 | 116 | // Poisson returns a random sample from the poisson distribution characterized 117 | // by lambda (mean). 118 | func Poisson(r *rand.Rand, lambda float64) int { 119 | if lambda < 30.0 { 120 | return poissonSmall(r, lambda) 121 | } 122 | return poissonLarge(r, lambda) 123 | } 124 | 125 | func poissonSmall(r *rand.Rand, lambda float64) int { 126 | // Algorithm due to Donald Knuth, 1969. 127 | p, l := float64(1.0), math.Exp(-lambda) 128 | k := 0 129 | for { 130 | if s := r.Float64(); s > 0.0 { 131 | k++ 132 | p = p * s 133 | } 134 | if p <= l { 135 | break 136 | } 137 | } 138 | return k - 1 139 | } 140 | 141 | func poissonLarge(r *rand.Rand, lambda float64) int { 142 | // "Rejection method PA" from "The Computer Generation of 143 | // Poisson Random Variables" by A. C. Atkinson, 144 | // Journal of the Royal Statistical Society Series C 145 | // (Applied Statistics) Vol. 28, No. 1. (1979) 146 | // The article is on pages 29-35. 147 | // The algorithm given here is on page 32. 148 | 149 | c := 0.767 - 3.36/lambda 150 | beta := math.Pi / math.Sqrt(3.0*lambda) 151 | alpha := beta * lambda 152 | k := math.Log(c) - lambda - math.Log(beta) 153 | 154 | for { 155 | u := r.Float64() 156 | if u == 0.0 { 157 | continue 158 | } 159 | x := (alpha - math.Log((1.0-u)/u)) / beta 160 | n := math.Floor(x + 0.5) 161 | if n < 0 { 162 | continue 163 | } 164 | v := r.Float64() 165 | if v == 0.0 { 166 | continue 167 | } 168 | y := alpha - beta*x 169 | temp := 1.0 + math.Exp(y) 170 | lhs := y + math.Log(v/(temp*temp)) 171 | rhs := k + n*math.Log(lambda) - logFactorial(n) 172 | if lhs <= rhs { 173 | return int(n) 174 | } 175 | } 176 | } 177 | 178 | func logFactorial(n float64) float64 { 179 | // Use Stirling's approximation, since the runtime library has 180 | // the gamma function math. 181 | n = math.Floor(n) 182 | ret, _ := math.Lgamma(n + 1) 183 | return ret 184 | } 185 | -------------------------------------------------------------------------------- /nike/schemes/schemes_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package schemes 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | "github.com/katzenpost/hpqc/rand" 14 | "github.com/katzenpost/hpqc/util" 15 | "github.com/katzenpost/hpqc/nike/x25519" 16 | ) 17 | 18 | func TestNIKEUnmarshaling(t *testing.T) { 19 | todo := All() 20 | 21 | testNike := func(s nike.Scheme) { 22 | pubkey1, privkey1, err := s.GenerateKeyPairFromEntropy(rand.Reader) 23 | require.NoError(t, err) 24 | 25 | pubkey1Blob, err := pubkey1.MarshalBinary() 26 | require.NoError(t, err) 27 | require.Equal(t, s.PublicKeySize(), len(pubkey1Blob)) 28 | 29 | t.Logf("pubkey1Blob is len %d", len(pubkey1Blob)) 30 | 31 | pubkey2, err := s.UnmarshalBinaryPublicKey(pubkey1Blob) 32 | require.NoError(t, err) 33 | refBytes := pubkey1.Bytes() 34 | require.Equal(t, refBytes, pubkey2.Bytes()) 35 | pubkey1.Reset() 36 | _, err = pubkey1.MarshalBinary() 37 | require.NoError(t, err) 38 | 39 | privkey1blob, err := privkey1.MarshalBinary() 40 | require.NoError(t, err) 41 | require.Equal(t, s.PrivateKeySize(), len(privkey1blob)) 42 | 43 | t.Logf("privkey1blob is len %d", len(privkey1blob)) 44 | refBytes = privkey1.Bytes() 45 | 46 | privkey1.Reset() 47 | _, err = privkey1.MarshalBinary() 48 | require.NoError(t, err) 49 | 50 | privkey2, err := s.UnmarshalBinaryPrivateKey(privkey1blob) 51 | require.NoError(t, err) 52 | 53 | require.Equal(t, refBytes, privkey2.Bytes()) 54 | require.Equal(t, s.PrivateKeySize(), len(privkey2.Bytes())) 55 | } 56 | 57 | for _, scheme := range todo { 58 | t.Logf("testing NIKE Scheme: %s", scheme.Name()) 59 | testNike(scheme) 60 | t.Log("OK") 61 | } 62 | } 63 | 64 | func TestRoundTripBytes(t *testing.T) { 65 | todo := All() 66 | 67 | testNike := func(s nike.Scheme) { 68 | pubkey1, privkey1, err := s.GenerateKeyPairFromEntropy(rand.Reader) 69 | require.NoError(t, err) 70 | pubkey2, privkey2, err := s.GenerateKeyPairFromEntropy(rand.Reader) 71 | require.NoError(t, err) 72 | pubkey2.FromBytes(pubkey1.Bytes()) 73 | require.Equal(t, pubkey1.Bytes(), pubkey2.Bytes()) 74 | privkey2.FromBytes(privkey1.Bytes()) 75 | require.Equal(t, privkey1.Bytes(), privkey2.Bytes()) 76 | } 77 | 78 | for _, scheme := range todo { 79 | t.Logf("testing NIKE Scheme: %s", scheme.Name()) 80 | testNike(scheme) 81 | t.Log("OK") 82 | } 83 | } 84 | 85 | func TestNIKEOps(t *testing.T) { 86 | todo := All() 87 | 88 | testNike := func(s nike.Scheme) { 89 | // test 1 90 | pubkey1, privkey1, err := s.GenerateKeyPairFromEntropy(rand.Reader) 91 | require.NoError(t, err) 92 | 93 | pubkey2, privkey2, err := s.GenerateKeyPairFromEntropy(rand.Reader) 94 | require.NoError(t, err) 95 | 96 | ss1 := s.DeriveSecret(privkey1, pubkey2) 97 | ss2 := s.DeriveSecret(privkey2, pubkey1) 98 | 99 | require.Equal(t, ss1, ss2) 100 | 101 | // test 2 102 | pubkey3 := privkey1.Public() 103 | require.Equal(t, pubkey1.Bytes(), pubkey3.Bytes()) 104 | blob1, err := pubkey1.MarshalBinary() 105 | require.NoError(t, err) 106 | blob2, err := pubkey3.MarshalBinary() 107 | require.NoError(t, err) 108 | require.Equal(t, blob1, blob2) 109 | 110 | // test 3 111 | pubkey4 := s.DerivePublicKey(privkey1) 112 | require.Equal(t, pubkey1.Bytes(), pubkey4.Bytes()) 113 | 114 | // test 4 115 | require.False(t, util.CtIsZero(pubkey1.Bytes())) 116 | pubblob1 := pubkey4.Bytes() 117 | pubkey4.Reset() 118 | pubblob2 := pubkey4.Bytes() 119 | require.False(t, util.CtIsZero(pubkey1.Bytes())) 120 | require.NotEqual(t, pubkey1.Bytes(), pubkey4.Bytes()) 121 | require.True(t, util.CtIsZero(pubkey4.Bytes())) 122 | require.NotEqual(t, pubblob1, pubblob2) 123 | 124 | // test 5 125 | privBlob1 := privkey2.Bytes() 126 | privkey2.Reset() 127 | privBlob2 := privkey2.Bytes() 128 | require.NotEqual(t, privBlob1, privBlob2) 129 | 130 | if strings.Contains(s.Name(), "NOBS") { 131 | return 132 | } 133 | 134 | // blinding operations test 135 | mixPub, mixPriv, err := s.GenerateKeyPairFromEntropy(rand.Reader) 136 | require.NoError(t, err) 137 | clientPub, clientPriv, err := s.GenerateKeyPairFromEntropy(rand.Reader) 138 | require.NoError(t, err) 139 | blindingFactor := s.GeneratePrivateKey(rand.Reader) 140 | pubkey1, err = s.UnmarshalBinaryPublicKey(s.DeriveSecret(clientPriv, mixPub)) 141 | require.NoError(t, err) 142 | value1 := s.Blind(pubkey1, blindingFactor) 143 | require.NoError(t, err) 144 | blinded := s.Blind(clientPub, blindingFactor) 145 | require.NoError(t, err) 146 | value2 := s.DeriveSecret(mixPriv, blinded) 147 | require.Equal(t, value1.Bytes(), value2) 148 | } 149 | 150 | for _, scheme := range todo { 151 | t.Logf("testing NIKE Scheme: %s", scheme.Name()) 152 | testNike(scheme) 153 | t.Log("OK") 154 | } 155 | } 156 | 157 | func BenchmarkNIKE(b *testing.B) { 158 | benchSchemes := []nike.Scheme{ 159 | x25519.Scheme(rand.Reader), 160 | } 161 | for _, scheme := range benchSchemes { 162 | scheme := scheme 163 | _, privkey1, err := scheme.GenerateKeyPairFromEntropy(rand.Reader) 164 | if err != nil { 165 | panic(err) 166 | } 167 | pubkey2, _, err := scheme.GenerateKeyPairFromEntropy(rand.Reader) 168 | if err != nil { 169 | panic(err) 170 | } 171 | b.Run(scheme.Name(), func(b *testing.B) { 172 | for i := 0; i < b.N; i++ { 173 | _ = scheme.DeriveSecret(privkey1, pubkey2) 174 | } 175 | }) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /sign/sphincsplus/sphincs.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // SPDX-FileCopyrightText: (c) 2022-2024 David Stainton 4 | // SPDX-License-Identifier: AGPL-3.0-only 5 | 6 | // Package sphincsplus implements interface wrapper around a specific parameterization of Sphincs+. 7 | package sphincsplus 8 | 9 | import ( 10 | "crypto" 11 | "crypto/hmac" 12 | "io" 13 | 14 | "golang.org/x/crypto/blake2b" 15 | 16 | sphincs "github.com/katzenpost/sphincsplus/ref" 17 | 18 | "github.com/katzenpost/hpqc/sign" 19 | "github.com/katzenpost/hpqc/sign/pem" 20 | ) 21 | 22 | const ( 23 | // KeySeedSize is the seed size used by NewKeyFromSeed to generate 24 | // a new key deterministically. 25 | KeySeedSize = 32 26 | ) 27 | 28 | type scheme struct{} 29 | 30 | var sch *scheme = &scheme{} 31 | 32 | func Scheme() *scheme { return sch } 33 | 34 | var _ sign.Scheme = (*scheme)(nil) 35 | var _ sign.PublicKey = (*publicKey)(nil) 36 | var _ sign.PrivateKey = (*privateKey)(nil) 37 | 38 | func (s *scheme) Name() string { 39 | return "Sphincs+" 40 | } 41 | 42 | func (s *scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { 43 | priv, pub := sphincs.NewKeypair() 44 | pubkey := &publicKey{ 45 | scheme: Scheme(), 46 | publicKey: pub, 47 | } 48 | privkey := &privateKey{ 49 | scheme: Scheme(), 50 | publicKey: pubkey, 51 | privateKey: priv, 52 | } 53 | return pubkey, privkey, nil 54 | } 55 | 56 | func (s *scheme) Sign(sk sign.PrivateKey, message []byte, opts *sign.SignatureOpts) []byte { 57 | sig, err := sk.Sign(nil, message, nil) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return sig 62 | } 63 | 64 | func (s *scheme) Verify(pk sign.PublicKey, message []byte, signature []byte, opts *sign.SignatureOpts) bool { 65 | return pk.(*publicKey).Verify(signature, message) 66 | } 67 | 68 | func (s *scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { 69 | // NOTE(david): we use the reference implementation of Sphincs+ which does not 70 | // have an API that allows for using our own entropy source or seed for key generation. 71 | // The way to fix this is to use a different implementation of Sphincs+. 72 | panic("DeriveKey not implemented") 73 | return nil, nil 74 | } 75 | 76 | func (s *scheme) UnmarshalBinaryPublicKey(b []byte) (sign.PublicKey, error) { 77 | pubKey := &publicKey{ 78 | publicKey: new(sphincs.PublicKey), 79 | } 80 | err := pubKey.FromBytes(b) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return pubKey, nil 85 | } 86 | 87 | // UnmarshalBinaryPrivateKey loads a private key from byte slice. 88 | func (s *scheme) UnmarshalBinaryPrivateKey(b []byte) (sign.PrivateKey, error) { 89 | privKey := &privateKey{ 90 | privateKey: new(sphincs.PrivateKey), 91 | } 92 | err := privKey.FromBytes(b) 93 | if err != nil { 94 | return nil, err 95 | } 96 | return privKey, nil 97 | } 98 | 99 | func (s *scheme) PrivateKeySize() int { 100 | return sphincs.PrivateKeySize 101 | } 102 | 103 | func (s *scheme) PublicKeySize() int { 104 | return sphincs.PublicKeySize 105 | } 106 | 107 | func (s *scheme) SignatureSize() int { 108 | return sphincs.SignatureSize 109 | } 110 | 111 | func (s *scheme) SeedSize() int { 112 | return KeySeedSize 113 | } 114 | 115 | func (s *scheme) SupportsContext() bool { 116 | return false 117 | } 118 | 119 | type privateKey struct { 120 | scheme *scheme 121 | privateKey *sphincs.PrivateKey 122 | publicKey *publicKey 123 | } 124 | 125 | // sign.PublicKey interface 126 | 127 | func (p *privateKey) Scheme() sign.Scheme { 128 | return p.scheme 129 | } 130 | 131 | func (p *privateKey) Equal(key crypto.PrivateKey) bool { 132 | return hmac.Equal(key.(*privateKey).Bytes(), p.Bytes()) 133 | } 134 | 135 | func (p *privateKey) Public() crypto.PublicKey { 136 | return p.publicKey 137 | } 138 | 139 | func (p *privateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 140 | return p.privateKey.Sign(digest), nil 141 | } 142 | 143 | func (p *privateKey) MarshalBinary() ([]byte, error) { 144 | return p.Bytes(), nil 145 | } 146 | 147 | func (p *privateKey) UnmarshalBinary(b []byte) error { 148 | return p.FromBytes(b) 149 | } 150 | 151 | // end of sign.PublicKey interface 152 | 153 | func (p *privateKey) Reset() { 154 | p.privateKey.Reset() 155 | } 156 | 157 | func (p *privateKey) Bytes() []byte { 158 | return p.privateKey.Bytes() 159 | } 160 | 161 | func (p *privateKey) FromBytes(data []byte) error { 162 | return p.privateKey.FromBytes(data) 163 | } 164 | 165 | type publicKey struct { 166 | scheme *scheme 167 | publicKey *sphincs.PublicKey 168 | } 169 | 170 | // sign.PublicKey interface 171 | 172 | func (p *publicKey) Scheme() sign.Scheme { 173 | return p.scheme 174 | } 175 | 176 | func (p *publicKey) Equal(key crypto.PublicKey) bool { 177 | return hmac.Equal(key.(*publicKey).Bytes(), p.Bytes()) 178 | } 179 | 180 | func (p *publicKey) MarshalBinary() ([]byte, error) { 181 | return p.Bytes(), nil 182 | } 183 | 184 | func (p *publicKey) MarshalText() (text []byte, err error) { 185 | return pem.ToPublicPEMBytes(p), nil 186 | } 187 | 188 | // end of sign.PublicKey interface 189 | 190 | func (p *publicKey) Reset() { 191 | p.publicKey.Reset() 192 | } 193 | 194 | func (p *publicKey) Bytes() []byte { 195 | return p.publicKey.Bytes() 196 | } 197 | 198 | func (p *publicKey) FromBytes(data []byte) error { 199 | return p.publicKey.FromBytes(data) 200 | } 201 | 202 | func (p *publicKey) Verify(sig, message []byte) bool { 203 | return p.publicKey.Verify(sig, message) 204 | } 205 | 206 | func (p *publicKey) Sum256() [32]byte { 207 | return blake2b.Sum256(p.Bytes()) 208 | } 209 | -------------------------------------------------------------------------------- /sign/ed25519/eddsa_test.go: -------------------------------------------------------------------------------- 1 | // eddsa_test.go - Test eddsa wrapper signature scheme tests. 2 | // 3 | // Copyright (C) 2022 David Stainton. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as 7 | // published by the Free Software Foundation, either version 3 of the 8 | // License, or (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package ed25519 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | 26 | "github.com/katzenpost/hpqc/util" 27 | ) 28 | 29 | func TestEddsaScheme(t *testing.T) { 30 | t.Parallel() 31 | message := []byte("hello world") 32 | pubKey, privKey, err := Scheme().GenerateKey() 33 | require.NoError(t, err) 34 | signature := Scheme().Sign(privKey, message, nil) 35 | require.Equal(t, len(signature), Scheme().SignatureSize()) 36 | ok := Scheme().Verify(pubKey, message, signature, nil) 37 | require.True(t, ok) 38 | } 39 | 40 | func TestEddsaSchemeTextUnmarshaler(t *testing.T) { 41 | t.Parallel() 42 | message := []byte("hello world") 43 | pubKey, privKey, err := Scheme().GenerateKey() 44 | require.NoError(t, err) 45 | 46 | pubKeyText, err := pubKey.MarshalBinary() 47 | require.NoError(t, err) 48 | 49 | pubKey2, err := Scheme().UnmarshalBinaryPublicKey(pubKeyText) 50 | require.NoError(t, err) 51 | 52 | signature := Scheme().Sign(privKey, message, nil) 53 | 54 | ok := Scheme().Verify(pubKey, message, signature, nil) 55 | require.True(t, ok) 56 | 57 | ok = Scheme().Verify(pubKey2, message, signature, nil) 58 | require.True(t, ok) 59 | } 60 | 61 | func TestKeypair(t *testing.T) { 62 | t.Parallel() 63 | 64 | var shortBuffer = []byte("Short Buffer") 65 | 66 | _, privKey, err := Scheme().GenerateKey() 67 | require.NoError(t, err, "NewKeypair()") 68 | 69 | var privKey2 PrivateKey 70 | require.Error(t, privKey2.FromBytes(shortBuffer)) 71 | 72 | privKeyBlob, err := privKey.MarshalBinary() 73 | require.NoError(t, err) 74 | 75 | err = privKey2.FromBytes(privKeyBlob) 76 | require.NoError(t, err) 77 | 78 | privKeyBlob2, err := privKey2.MarshalBinary() 79 | require.NoError(t, err) 80 | 81 | require.Equal(t, privKeyBlob2, privKeyBlob) 82 | 83 | privKey2.Reset() 84 | require.True(t, util.CtIsZero(privKey2.privKey)) 85 | 86 | var pubKey PublicKey 87 | require.Error(t, pubKey.FromBytes(shortBuffer)) 88 | 89 | blob, err := privKey.(*PrivateKey).PublicKey().MarshalBinary() 90 | require.NoError(t, err) 91 | 92 | err = pubKey.FromBytes(blob) 93 | require.NoError(t, err) 94 | require.Equal(t, privKey.Public(), &pubKey) 95 | 96 | pkArr := pubKey.ByteArray() 97 | 98 | blob, err = privKey.Public().(*PublicKey).MarshalBinary() 99 | require.NoError(t, err) 100 | require.Equal(t, blob, pkArr[:]) 101 | } 102 | 103 | func TestEdDSAOps(t *testing.T) { 104 | t.Parallel() 105 | assert := assert.New(t) 106 | 107 | _, privKey, err := Scheme().GenerateKey() 108 | require.NoError(t, err, "NewKeypair()") 109 | pubKey := privKey.Public().(*PublicKey) 110 | 111 | msg := []byte("The year was 2081, and everybody was finally equal. They weren't only equal before God and the law. They were equal every which way. Nobody was smarter than anybody else. Nobody was better looking than anybody else. Nobody was stronger or quicker than anybody else. All this equality was due to the 211th, 212th, and 213th Amendments to the Constitution, and to the unceasing vigilance of agents of the United States Handicapper General.") 112 | 113 | sig, err := privKey.Sign(nil, msg, nil) 114 | require.NoError(t, err) 115 | 116 | assert.Equal(SignatureSize, len(sig), "Sign() length") 117 | assert.True(pubKey.Verify(sig, msg), "Verify(sig, msg)") 118 | assert.False(pubKey.Verify(sig, msg[:16]), "Verify(sig, msg[:16])") 119 | } 120 | 121 | func TestCheckEdDSA(t *testing.T) { 122 | t.Parallel() 123 | // check that EdDSA signing works like the first test vector in 124 | // https://ed25519.cr.yp.to/python/sign.input 125 | // (this is a sanity check to ensure (R,s) is computed as it should 126 | // as it was non-obvious to me that the nonce is being clamped 127 | assert := assert.New(t) 128 | vector_signed := [64]byte{229, 86, 67, 0, 195, 96, 172, 114, 144, 134, 226, 204, 128, 110, 130, 138, 132, 135, 127, 30, 184, 229, 217, 116, 216, 115, 224, 101, 34, 73, 1, 85, 95, 184, 130, 21, 144, 163, 59, 172, 198, 30, 57, 112, 28, 249, 180, 107, 210, 91, 245, 240, 89, 91, 190, 36, 101, 81, 65, 67, 142, 122, 16, 11} 129 | tsk := [64]byte{157, 97, 177, 157, 239, 253, 90, 96, 186, 132, 74, 244, 146, 236, 44, 196, 68, 73, 197, 105, 123, 50, 105, 25, 112, 59, 172, 3, 28, 174, 127, 96, 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26} 130 | tpk := [32]byte{215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26} 131 | rsk := new(PrivateKey) 132 | assert.NoError(rsk.FromBytes(tsk[:])) 133 | assert.Equal(tpk[:], rsk.PublicKey().Bytes()) 134 | actual_signed, err := rsk.Sign(nil, []byte{}, nil) 135 | require.NoError(t, err) 136 | assert.Equal(vector_signed[:], actual_signed) 137 | verify_res := rsk.PublicKey().Verify(vector_signed[:], []byte{}) 138 | assert.Equal(true, verify_res) 139 | // and 1 was NOT the message, so that shouldn't check out: 140 | verify_res = rsk.PublicKey().Verify(vector_signed[:], []byte{1}) 141 | assert.Equal(false, verify_res) 142 | } 143 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | codeberg.org/vula/highctidh v1.0.2024092800 h1:mvAQCDPgTTwo0/w1ZztxOEKp8CnH2+q5zf+3vKR79Jc= 2 | codeberg.org/vula/highctidh v1.0.2024092800/go.mod h1:admvznk7GhsrDih/BMy7jbIv9Y86U9vj7S5FVoquF/g= 3 | filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= 4 | filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= 5 | filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1 h1:xbdqh5aDZeO0XqW896qVjKnAqRji9nkIwmsBEEbCA10= 6 | filippo.io/mlkem768 v0.0.0-20240221181710-5ce91625fdc1/go.mod h1:mIEHrcJ2xBlJRQwnRO0ujmZ+Rt6m6eNeCPq8E3Wkths= 7 | github.com/agl/gcmsiv v0.0.0-20190418185415-e8dcd2f151dc h1:MB338WDPuyQ8qXRiSRK2NVTv0EmLYsHXZAevEiQO1+c= 8 | github.com/agl/gcmsiv v0.0.0-20190418185415-e8dcd2f151dc/go.mod h1:5joDAvk82M2Cx1X8mAL5Orvhy5lfW4BjrTCW65wbvRo= 9 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 10 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 15 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 16 | github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38= 17 | github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ= 18 | github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73 h1:d3rq/Tz+RJ5h1xk6Lt3jbObJN3WhvZm7rV41OCIzUyI= 19 | github.com/henrydcase/nobs v0.0.0-20230313231516-25b66236df73/go.mod h1:ptK2MJqVLVEa/V/oK8n+MEyUDCSjSylW+jeNmCG1DJo= 20 | github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556 h1:9gHByAWH1LydGefFGorN1ZBRZ/Oz9iozdzMvRTWpyRw= 21 | github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556/go.mod h1:d9kxwmGOcutgP6bQwr2xaLInaW5yJsxsoPRyUIG0J/E= 22 | github.com/katzenpost/chacha20poly1305 v0.0.0-20211026103954-7b6fb2fc0129 h1:BkXIK2/3bAtqFbzea4tMVfhu8BQ9j05yCVy/z8iCz3s= 23 | github.com/katzenpost/chacha20poly1305 v0.0.0-20211026103954-7b6fb2fc0129/go.mod h1:moU3dWG1uk4xayUNdUPs2Q3dx2Zsk5i9Ysfx+ujQrwM= 24 | github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c h1:FYy03rLIjdyjklBOI6YSCb3q7OubTx0dVDWYOgDsvA8= 25 | github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c/go.mod h1:+EBrwiGYs9S+qZqaqxujN1CReTNCMAG6p+31KkEDeeA= 26 | github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8 h1:TsKxH0x2RUwf5rBw67k15bqVM3oVbexA9oaTZQLIy3Y= 27 | github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8/go.mod h1:Hmcrwom7jcEmGdo0CsyuJNnldPeyS+M07FuCbo7I8fw= 28 | github.com/katzenpost/sphincsplus v0.0.2 h1:W1UWejLK62Lk0uK2R08H/sWEaQrRHWCaMEKO181SoOE= 29 | github.com/katzenpost/sphincsplus v0.0.2/go.mod h1:ChO9+ojgCH1yEuplGgW4mSI1FwZWtyEmEkG1xL3w264= 30 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 31 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 32 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 33 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 37 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 38 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 39 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 40 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 41 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 42 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 43 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 44 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 45 | gitlab.com/elixxir/crypto v0.0.9 h1:vt7+yRvGd/G9VtpQCaXa7EKPmHTgThf2hXbzZ1vlU6I= 46 | gitlab.com/elixxir/crypto v0.0.9/go.mod h1:KV3F+kO0VVusLIKPqSKMk4HZ4yQiS12SaIsOXg9LJb0= 47 | gitlab.com/xx_network/crypto v0.0.6 h1:+C44rBhclcbWrGa5EOic5yDF3NrXAbXScCb/mXmm3Ro= 48 | gitlab.com/xx_network/crypto v0.0.6/go.mod h1:C69/+XTiqJvKkYzcyA47+LdxvAofl9AzR/Nyo36y9hs= 49 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 50 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 51 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 52 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 53 | golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 58 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 59 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 60 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 61 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 62 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 63 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 64 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 65 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 66 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 67 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh511/ctidh.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh511 5 | 6 | import ( 7 | "encoding/base64" 8 | "io" 9 | 10 | ctidh "codeberg.org/vula/highctidh/src/ctidh511" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | ) 14 | 15 | // CTIDH implements the Nike interface using our CTIDH module. 16 | type scheme struct{} 17 | 18 | var sch nike.Scheme = &scheme{} 19 | 20 | // Scheme returns a NIKE Scheme interface. 21 | func Scheme() nike.Scheme { return sch } 22 | 23 | var _ nike.PrivateKey = (*PrivateKey)(nil) 24 | var _ nike.PublicKey = (*PublicKey)(nil) 25 | var _ nike.Scheme = (*scheme)(nil) 26 | 27 | func (e *scheme) Name() string { 28 | return "ctidh511" 29 | } 30 | 31 | // PublicKeySize returns the size in bytes of the public key. 32 | func (e *scheme) PublicKeySize() int { 33 | return ctidh.PublicKeySize 34 | } 35 | 36 | // PrivateKeySize returns the size in bytes of the private key. 37 | func (e *scheme) PrivateKeySize() int { 38 | return ctidh.PrivateKeySize 39 | } 40 | 41 | // NewEmptyPublicKey returns an uninitialized 42 | // PublicKey which is suitable to be loaded 43 | // via some serialization format via FromBytes 44 | // or FromPEMFile methods. 45 | func (e *scheme) NewEmptyPublicKey() nike.PublicKey { 46 | return &PublicKey{ 47 | publicKey: ctidh.NewEmptyPublicKey(), 48 | } 49 | } 50 | 51 | // NewEmptyPrivateKey returns an uninitialized 52 | // PrivateKey which is suitable to be loaded 53 | // via some serialization format via FromBytes 54 | // or FromPEMFile methods. 55 | func (e *scheme) NewEmptyPrivateKey() nike.PrivateKey { 56 | return &PrivateKey{ 57 | privateKey: ctidh.NewEmptyPrivateKey(), 58 | } 59 | } 60 | 61 | func (e *scheme) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 62 | return &PrivateKey{ 63 | privateKey: ctidh.GeneratePrivateKey(rng), 64 | } 65 | } 66 | 67 | func (e *scheme) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 68 | privKey, pubKey := ctidh.GenerateKeyPairWithRNG(rng) 69 | return &PublicKey{ 70 | publicKey: pubKey, 71 | }, &PrivateKey{ 72 | privateKey: privKey, 73 | }, nil 74 | } 75 | 76 | // GenerateKeyPair creates a new key pair. 77 | func (e *scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 78 | privKey, pubKey := ctidh.GenerateKeyPair() 79 | return &PublicKey{ 80 | publicKey: pubKey, 81 | }, &PrivateKey{ 82 | privateKey: privKey, 83 | }, nil 84 | } 85 | 86 | // DeriveSecret derives a shared secret given a private key 87 | // from one party and a public key from another. 88 | func (e *scheme) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 89 | return ctidh.DeriveSecret(privKey.(*PrivateKey).privateKey, pubKey.(*PublicKey).publicKey) 90 | } 91 | 92 | // DerivePublicKey derives a public key given a private key. 93 | func (e *scheme) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 94 | return &PublicKey{ 95 | publicKey: ctidh.DerivePublicKey(privKey.(*PrivateKey).privateKey), 96 | } 97 | } 98 | 99 | func (e *scheme) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) nike.PublicKey { 100 | blinded, err := ctidh.Blind( 101 | blindingFactor.(*PrivateKey).privateKey, 102 | groupMember.(*PublicKey).publicKey, 103 | ) 104 | if err != nil { 105 | panic(err) 106 | } 107 | return &PublicKey{ 108 | publicKey: blinded, 109 | } 110 | } 111 | 112 | func (e *scheme) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 113 | pubkey := ctidh.NewEmptyPublicKey() 114 | err := pubkey.FromBytes(b) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return &PublicKey{ 119 | publicKey: pubkey, 120 | }, nil 121 | } 122 | 123 | func (e *scheme) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 124 | privkey := ctidh.NewEmptyPrivateKey() 125 | err := privkey.FromBytes(b) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return &PrivateKey{ 130 | privateKey: privkey, 131 | }, nil 132 | } 133 | 134 | type PublicKey struct { 135 | publicKey *ctidh.PublicKey 136 | } 137 | 138 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 139 | return p.publicKey.Blind(blindingFactor.(*PrivateKey).privateKey) 140 | } 141 | 142 | func (p *PublicKey) Reset() { 143 | p.publicKey.Reset() 144 | } 145 | 146 | func (p *PublicKey) Bytes() []byte { 147 | return p.publicKey.Bytes() 148 | } 149 | 150 | func (p *PublicKey) FromBytes(data []byte) error { 151 | return p.publicKey.FromBytes(data) 152 | } 153 | 154 | // MarshalBinary is an implementation of a method on the 155 | // BinaryMarshaler interface defined in https://golang.org/pkg/encoding/ 156 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 157 | return p.Bytes(), nil 158 | } 159 | 160 | // UnmarshalBinary is an implementation of a method on the 161 | // BinaryUnmarshaler interface defined in https://golang.org/pkg/encoding/ 162 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 163 | return p.FromBytes(data) 164 | } 165 | 166 | // MarshalText is an implementation of a method on the 167 | // TextMarshaler interface defined in https://golang.org/pkg/encoding/ 168 | func (p *PublicKey) MarshalText() ([]byte, error) { 169 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 170 | } 171 | 172 | // UnmarshalText is an implementation of a method on the 173 | // TextUnmarshaler interface defined in https://golang.org/pkg/encoding/ 174 | func (p *PublicKey) UnmarshalText(data []byte) error { 175 | raw, err := base64.StdEncoding.DecodeString(string(data)) 176 | if err != nil { 177 | return err 178 | } 179 | return p.FromBytes(raw) 180 | } 181 | 182 | type PrivateKey struct { 183 | privateKey *ctidh.PrivateKey 184 | } 185 | 186 | func (p *PrivateKey) Public() nike.PublicKey { 187 | return &PublicKey{ 188 | publicKey: p.privateKey.Public(), 189 | } 190 | } 191 | 192 | func (p *PrivateKey) Reset() { 193 | p.privateKey.Reset() 194 | } 195 | 196 | func (p *PrivateKey) Bytes() []byte { 197 | return p.privateKey.Bytes() 198 | } 199 | 200 | func (p *PrivateKey) FromBytes(data []byte) error { 201 | return p.privateKey.FromBytes(data) 202 | } 203 | 204 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 205 | return p.privateKey.MarshalBinary() 206 | } 207 | 208 | func (p *PrivateKey) MarshalText() ([]byte, error) { 209 | return p.privateKey.MarshalText() 210 | } 211 | 212 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 213 | return p.privateKey.UnmarshalBinary(data) 214 | } 215 | 216 | func (p *PrivateKey) UnmarshalText(data []byte) error { 217 | return p.privateKey.UnmarshalText(data) 218 | } 219 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh512/ctidh.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh512 5 | 6 | import ( 7 | "encoding/base64" 8 | "io" 9 | 10 | ctidh "codeberg.org/vula/highctidh/src/ctidh512" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | ) 14 | 15 | // CTIDH implements the Nike interface using our CTIDH module. 16 | type scheme struct{} 17 | 18 | var sch nike.Scheme = &scheme{} 19 | 20 | // Scheme returns a NIKE Scheme interface. 21 | func Scheme() nike.Scheme { return sch } 22 | 23 | var _ nike.PrivateKey = (*PrivateKey)(nil) 24 | var _ nike.PublicKey = (*PublicKey)(nil) 25 | var _ nike.Scheme = (*scheme)(nil) 26 | 27 | func (e *scheme) Name() string { 28 | return "ctidh512" 29 | } 30 | 31 | // PublicKeySize returns the size in bytes of the public key. 32 | func (e *scheme) PublicKeySize() int { 33 | return ctidh.PublicKeySize 34 | } 35 | 36 | // PrivateKeySize returns the size in bytes of the private key. 37 | func (e *scheme) PrivateKeySize() int { 38 | return ctidh.PrivateKeySize 39 | } 40 | 41 | // NewEmptyPublicKey returns an uninitialized 42 | // PublicKey which is suitable to be loaded 43 | // via some serialization format via FromBytes 44 | // or FromPEMFile methods. 45 | func (e *scheme) NewEmptyPublicKey() nike.PublicKey { 46 | return &PublicKey{ 47 | publicKey: ctidh.NewEmptyPublicKey(), 48 | } 49 | } 50 | 51 | // NewEmptyPrivateKey returns an uninitialized 52 | // PrivateKey which is suitable to be loaded 53 | // via some serialization format via FromBytes 54 | // or FromPEMFile methods. 55 | func (e *scheme) NewEmptyPrivateKey() nike.PrivateKey { 56 | return &PrivateKey{ 57 | privateKey: ctidh.NewEmptyPrivateKey(), 58 | } 59 | } 60 | 61 | func (e *scheme) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 62 | return &PrivateKey{ 63 | privateKey: ctidh.GeneratePrivateKey(rng), 64 | } 65 | } 66 | 67 | func (e *scheme) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 68 | privKey, pubKey := ctidh.GenerateKeyPairWithRNG(rng) 69 | return &PublicKey{ 70 | publicKey: pubKey, 71 | }, &PrivateKey{ 72 | privateKey: privKey, 73 | }, nil 74 | } 75 | 76 | // GenerateKeyPair creates a new key pair. 77 | func (e *scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 78 | privKey, pubKey := ctidh.GenerateKeyPair() 79 | return &PublicKey{ 80 | publicKey: pubKey, 81 | }, &PrivateKey{ 82 | privateKey: privKey, 83 | }, nil 84 | } 85 | 86 | // DeriveSecret derives a shared secret given a private key 87 | // from one party and a public key from another. 88 | func (e *scheme) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 89 | return ctidh.DeriveSecret(privKey.(*PrivateKey).privateKey, pubKey.(*PublicKey).publicKey) 90 | } 91 | 92 | // DerivePublicKey derives a public key given a private key. 93 | func (e *scheme) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 94 | return &PublicKey{ 95 | publicKey: ctidh.DerivePublicKey(privKey.(*PrivateKey).privateKey), 96 | } 97 | } 98 | 99 | func (e *scheme) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) nike.PublicKey { 100 | blinded, err := ctidh.Blind( 101 | blindingFactor.(*PrivateKey).privateKey, 102 | groupMember.(*PublicKey).publicKey, 103 | ) 104 | if err != nil { 105 | panic(err) 106 | } 107 | return &PublicKey{ 108 | publicKey: blinded, 109 | } 110 | } 111 | 112 | func (e *scheme) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 113 | pubkey := ctidh.NewEmptyPublicKey() 114 | err := pubkey.FromBytes(b) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return &PublicKey{ 119 | publicKey: pubkey, 120 | }, nil 121 | } 122 | 123 | func (e *scheme) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 124 | privkey := ctidh.NewEmptyPrivateKey() 125 | err := privkey.FromBytes(b) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return &PrivateKey{ 130 | privateKey: privkey, 131 | }, nil 132 | } 133 | 134 | type PublicKey struct { 135 | publicKey *ctidh.PublicKey 136 | } 137 | 138 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 139 | return p.publicKey.Blind(blindingFactor.(*PrivateKey).privateKey) 140 | } 141 | 142 | func (p *PublicKey) Reset() { 143 | p.publicKey.Reset() 144 | } 145 | 146 | func (p *PublicKey) Bytes() []byte { 147 | return p.publicKey.Bytes() 148 | } 149 | 150 | func (p *PublicKey) FromBytes(data []byte) error { 151 | return p.publicKey.FromBytes(data) 152 | } 153 | 154 | // MarshalBinary is an implementation of a method on the 155 | // BinaryMarshaler interface defined in https://golang.org/pkg/encoding/ 156 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 157 | return p.Bytes(), nil 158 | } 159 | 160 | // UnmarshalBinary is an implementation of a method on the 161 | // BinaryUnmarshaler interface defined in https://golang.org/pkg/encoding/ 162 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 163 | return p.FromBytes(data) 164 | } 165 | 166 | // MarshalText is an implementation of a method on the 167 | // TextMarshaler interface defined in https://golang.org/pkg/encoding/ 168 | func (p *PublicKey) MarshalText() ([]byte, error) { 169 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 170 | } 171 | 172 | // UnmarshalText is an implementation of a method on the 173 | // TextUnmarshaler interface defined in https://golang.org/pkg/encoding/ 174 | func (p *PublicKey) UnmarshalText(data []byte) error { 175 | raw, err := base64.StdEncoding.DecodeString(string(data)) 176 | if err != nil { 177 | return err 178 | } 179 | return p.FromBytes(raw) 180 | } 181 | 182 | type PrivateKey struct { 183 | privateKey *ctidh.PrivateKey 184 | } 185 | 186 | func (p *PrivateKey) Public() nike.PublicKey { 187 | return &PublicKey{ 188 | publicKey: p.privateKey.Public(), 189 | } 190 | } 191 | 192 | func (p *PrivateKey) Reset() { 193 | p.privateKey.Reset() 194 | } 195 | 196 | func (p *PrivateKey) Bytes() []byte { 197 | return p.privateKey.Bytes() 198 | } 199 | 200 | func (p *PrivateKey) FromBytes(data []byte) error { 201 | return p.privateKey.FromBytes(data) 202 | } 203 | 204 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 205 | return p.privateKey.MarshalBinary() 206 | } 207 | 208 | func (p *PrivateKey) MarshalText() ([]byte, error) { 209 | return p.privateKey.MarshalText() 210 | } 211 | 212 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 213 | return p.privateKey.UnmarshalBinary(data) 214 | } 215 | 216 | func (p *PrivateKey) UnmarshalText(data []byte) error { 217 | return p.privateKey.UnmarshalText(data) 218 | } 219 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh1024/ctidh.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh1024 5 | 6 | import ( 7 | "encoding/base64" 8 | "io" 9 | 10 | ctidh "codeberg.org/vula/highctidh/src/ctidh1024" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | ) 14 | 15 | // CTIDH implements the Nike interface using our CTIDH module. 16 | type scheme struct{} 17 | 18 | var sch nike.Scheme = &scheme{} 19 | 20 | // Scheme returns a NIKE Scheme interface. 21 | func Scheme() nike.Scheme { return sch } 22 | 23 | var _ nike.PrivateKey = (*PrivateKey)(nil) 24 | var _ nike.PublicKey = (*PublicKey)(nil) 25 | var _ nike.Scheme = (*scheme)(nil) 26 | 27 | func (e *scheme) Name() string { 28 | return "ctidh1024" 29 | } 30 | 31 | // PublicKeySize returns the size in bytes of the public key. 32 | func (e *scheme) PublicKeySize() int { 33 | return ctidh.PublicKeySize 34 | } 35 | 36 | // PrivateKeySize returns the size in bytes of the private key. 37 | func (e *scheme) PrivateKeySize() int { 38 | return ctidh.PrivateKeySize 39 | } 40 | 41 | // NewEmptyPublicKey returns an uninitialized 42 | // PublicKey which is suitable to be loaded 43 | // via some serialization format via FromBytes 44 | // or FromPEMFile methods. 45 | func (e *scheme) NewEmptyPublicKey() nike.PublicKey { 46 | return &PublicKey{ 47 | publicKey: ctidh.NewEmptyPublicKey(), 48 | } 49 | } 50 | 51 | // NewEmptyPrivateKey returns an uninitialized 52 | // PrivateKey which is suitable to be loaded 53 | // via some serialization format via FromBytes 54 | // or FromPEMFile methods. 55 | func (e *scheme) NewEmptyPrivateKey() nike.PrivateKey { 56 | return &PrivateKey{ 57 | privateKey: ctidh.NewEmptyPrivateKey(), 58 | } 59 | } 60 | 61 | func (e *scheme) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 62 | return &PrivateKey{ 63 | privateKey: ctidh.GeneratePrivateKey(rng), 64 | } 65 | } 66 | 67 | func (e *scheme) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 68 | privKey, pubKey := ctidh.GenerateKeyPairWithRNG(rng) 69 | return &PublicKey{ 70 | publicKey: pubKey, 71 | }, &PrivateKey{ 72 | privateKey: privKey, 73 | }, nil 74 | } 75 | 76 | // GenerateKeyPair creates a new key pair. 77 | func (e *scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 78 | privKey, pubKey := ctidh.GenerateKeyPair() 79 | return &PublicKey{ 80 | publicKey: pubKey, 81 | }, &PrivateKey{ 82 | privateKey: privKey, 83 | }, nil 84 | } 85 | 86 | // DeriveSecret derives a shared secret given a private key 87 | // from one party and a public key from another. 88 | func (e *scheme) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 89 | return ctidh.DeriveSecret(privKey.(*PrivateKey).privateKey, pubKey.(*PublicKey).publicKey) 90 | } 91 | 92 | // DerivePublicKey derives a public key given a private key. 93 | func (e *scheme) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 94 | return &PublicKey{ 95 | publicKey: ctidh.DerivePublicKey(privKey.(*PrivateKey).privateKey), 96 | } 97 | } 98 | 99 | func (e *scheme) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) nike.PublicKey { 100 | blinded, err := ctidh.Blind( 101 | blindingFactor.(*PrivateKey).privateKey, 102 | groupMember.(*PublicKey).publicKey, 103 | ) 104 | if err != nil { 105 | panic(err) 106 | } 107 | return &PublicKey{ 108 | publicKey: blinded, 109 | } 110 | } 111 | 112 | func (e *scheme) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 113 | pubkey := ctidh.NewEmptyPublicKey() 114 | err := pubkey.FromBytes(b) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return &PublicKey{ 119 | publicKey: pubkey, 120 | }, nil 121 | } 122 | 123 | func (e *scheme) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 124 | privkey := ctidh.NewEmptyPrivateKey() 125 | err := privkey.FromBytes(b) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return &PrivateKey{ 130 | privateKey: privkey, 131 | }, nil 132 | } 133 | 134 | type PublicKey struct { 135 | publicKey *ctidh.PublicKey 136 | } 137 | 138 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 139 | return p.publicKey.Blind(blindingFactor.(*PrivateKey).privateKey) 140 | } 141 | 142 | func (p *PublicKey) Reset() { 143 | p.publicKey.Reset() 144 | } 145 | 146 | func (p *PublicKey) Bytes() []byte { 147 | return p.publicKey.Bytes() 148 | } 149 | 150 | func (p *PublicKey) FromBytes(data []byte) error { 151 | return p.publicKey.FromBytes(data) 152 | } 153 | 154 | // MarshalBinary is an implementation of a method on the 155 | // BinaryMarshaler interface defined in https://golang.org/pkg/encoding/ 156 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 157 | return p.Bytes(), nil 158 | } 159 | 160 | // UnmarshalBinary is an implementation of a method on the 161 | // BinaryUnmarshaler interface defined in https://golang.org/pkg/encoding/ 162 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 163 | return p.FromBytes(data) 164 | } 165 | 166 | // MarshalText is an implementation of a method on the 167 | // TextMarshaler interface defined in https://golang.org/pkg/encoding/ 168 | func (p *PublicKey) MarshalText() ([]byte, error) { 169 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 170 | } 171 | 172 | // UnmarshalText is an implementation of a method on the 173 | // TextUnmarshaler interface defined in https://golang.org/pkg/encoding/ 174 | func (p *PublicKey) UnmarshalText(data []byte) error { 175 | raw, err := base64.StdEncoding.DecodeString(string(data)) 176 | if err != nil { 177 | return err 178 | } 179 | return p.FromBytes(raw) 180 | } 181 | 182 | type PrivateKey struct { 183 | privateKey *ctidh.PrivateKey 184 | } 185 | 186 | func (p *PrivateKey) Public() nike.PublicKey { 187 | return &PublicKey{ 188 | publicKey: p.privateKey.Public(), 189 | } 190 | } 191 | 192 | func (p *PrivateKey) Reset() { 193 | p.privateKey.Reset() 194 | } 195 | 196 | func (p *PrivateKey) Bytes() []byte { 197 | return p.privateKey.Bytes() 198 | } 199 | 200 | func (p *PrivateKey) FromBytes(data []byte) error { 201 | return p.privateKey.FromBytes(data) 202 | } 203 | 204 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 205 | return p.privateKey.MarshalBinary() 206 | } 207 | 208 | func (p *PrivateKey) MarshalText() ([]byte, error) { 209 | return p.privateKey.MarshalText() 210 | } 211 | 212 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 213 | return p.privateKey.UnmarshalBinary(data) 214 | } 215 | 216 | func (p *PrivateKey) UnmarshalText(data []byte) error { 217 | return p.privateKey.UnmarshalText(data) 218 | } 219 | -------------------------------------------------------------------------------- /nike/ctidh/ctidh2048/ctidh.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (C) 2022-2024 David Stainton. 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package ctidh2048 5 | 6 | import ( 7 | "encoding/base64" 8 | "io" 9 | 10 | ctidh "codeberg.org/vula/highctidh/src/ctidh2048" 11 | 12 | "github.com/katzenpost/hpqc/nike" 13 | ) 14 | 15 | // CTIDH implements the Nike interface using our CTIDH module. 16 | type scheme struct{} 17 | 18 | var sch nike.Scheme = &scheme{} 19 | 20 | // Scheme returns a NIKE Scheme interface. 21 | func Scheme() nike.Scheme { return sch } 22 | 23 | var _ nike.PrivateKey = (*PrivateKey)(nil) 24 | var _ nike.PublicKey = (*PublicKey)(nil) 25 | var _ nike.Scheme = (*scheme)(nil) 26 | 27 | func (e *scheme) Name() string { 28 | return "ctidh2048" 29 | } 30 | 31 | // PublicKeySize returns the size in bytes of the public key. 32 | func (e *scheme) PublicKeySize() int { 33 | return ctidh.PublicKeySize 34 | } 35 | 36 | // PrivateKeySize returns the size in bytes of the private key. 37 | func (e *scheme) PrivateKeySize() int { 38 | return ctidh.PrivateKeySize 39 | } 40 | 41 | // NewEmptyPublicKey returns an uninitialized 42 | // PublicKey which is suitable to be loaded 43 | // via some serialization format via FromBytes 44 | // or FromPEMFile methods. 45 | func (e *scheme) NewEmptyPublicKey() nike.PublicKey { 46 | return &PublicKey{ 47 | publicKey: ctidh.NewEmptyPublicKey(), 48 | } 49 | } 50 | 51 | // NewEmptyPrivateKey returns an uninitialized 52 | // PrivateKey which is suitable to be loaded 53 | // via some serialization format via FromBytes 54 | // or FromPEMFile methods. 55 | func (e *scheme) NewEmptyPrivateKey() nike.PrivateKey { 56 | return &PrivateKey{ 57 | privateKey: ctidh.NewEmptyPrivateKey(), 58 | } 59 | } 60 | 61 | func (e *scheme) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 62 | return &PrivateKey{ 63 | privateKey: ctidh.GeneratePrivateKey(rng), 64 | } 65 | } 66 | 67 | func (e *scheme) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 68 | privKey, pubKey := ctidh.GenerateKeyPairWithRNG(rng) 69 | return &PublicKey{ 70 | publicKey: pubKey, 71 | }, &PrivateKey{ 72 | privateKey: privKey, 73 | }, nil 74 | } 75 | 76 | // GenerateKeyPair creates a new key pair. 77 | func (e *scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 78 | privKey, pubKey := ctidh.GenerateKeyPair() 79 | return &PublicKey{ 80 | publicKey: pubKey, 81 | }, &PrivateKey{ 82 | privateKey: privKey, 83 | }, nil 84 | } 85 | 86 | // DeriveSecret derives a shared secret given a private key 87 | // from one party and a public key from another. 88 | func (e *scheme) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 89 | return ctidh.DeriveSecret(privKey.(*PrivateKey).privateKey, pubKey.(*PublicKey).publicKey) 90 | } 91 | 92 | // DerivePublicKey derives a public key given a private key. 93 | func (e *scheme) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 94 | return &PublicKey{ 95 | publicKey: ctidh.DerivePublicKey(privKey.(*PrivateKey).privateKey), 96 | } 97 | } 98 | 99 | func (e *scheme) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) nike.PublicKey { 100 | blinded, err := ctidh.Blind( 101 | blindingFactor.(*PrivateKey).privateKey, 102 | groupMember.(*PublicKey).publicKey, 103 | ) 104 | if err != nil { 105 | panic(err) 106 | } 107 | return &PublicKey{ 108 | publicKey: blinded, 109 | } 110 | } 111 | 112 | func (e *scheme) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 113 | pubkey := ctidh.NewEmptyPublicKey() 114 | err := pubkey.FromBytes(b) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return &PublicKey{ 119 | publicKey: pubkey, 120 | }, nil 121 | } 122 | 123 | func (e *scheme) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 124 | privkey := ctidh.NewEmptyPrivateKey() 125 | err := privkey.FromBytes(b) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return &PrivateKey{ 130 | privateKey: privkey, 131 | }, nil 132 | } 133 | 134 | type PublicKey struct { 135 | publicKey *ctidh.PublicKey 136 | } 137 | 138 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 139 | return p.publicKey.Blind(blindingFactor.(*PrivateKey).privateKey) 140 | } 141 | 142 | func (p *PublicKey) Reset() { 143 | p.publicKey.Reset() 144 | } 145 | 146 | func (p *PublicKey) Bytes() []byte { 147 | return p.publicKey.Bytes() 148 | } 149 | 150 | func (p *PublicKey) FromBytes(data []byte) error { 151 | return p.publicKey.FromBytes(data) 152 | } 153 | 154 | // MarshalBinary is an implementation of a method on the 155 | // BinaryMarshaler interface defined in https://golang.org/pkg/encoding/ 156 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 157 | return p.Bytes(), nil 158 | } 159 | 160 | // UnmarshalBinary is an implementation of a method on the 161 | // BinaryUnmarshaler interface defined in https://golang.org/pkg/encoding/ 162 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 163 | return p.FromBytes(data) 164 | } 165 | 166 | // MarshalText is an implementation of a method on the 167 | // TextMarshaler interface defined in https://golang.org/pkg/encoding/ 168 | func (p *PublicKey) MarshalText() ([]byte, error) { 169 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 170 | } 171 | 172 | // UnmarshalText is an implementation of a method on the 173 | // TextUnmarshaler interface defined in https://golang.org/pkg/encoding/ 174 | func (p *PublicKey) UnmarshalText(data []byte) error { 175 | raw, err := base64.StdEncoding.DecodeString(string(data)) 176 | if err != nil { 177 | return err 178 | } 179 | return p.FromBytes(raw) 180 | } 181 | 182 | type PrivateKey struct { 183 | privateKey *ctidh.PrivateKey 184 | } 185 | 186 | func (p *PrivateKey) Public() nike.PublicKey { 187 | return &PublicKey{ 188 | publicKey: p.privateKey.Public(), 189 | } 190 | } 191 | 192 | func (p *PrivateKey) Reset() { 193 | p.privateKey.Reset() 194 | } 195 | 196 | func (p *PrivateKey) Bytes() []byte { 197 | return p.privateKey.Bytes() 198 | } 199 | 200 | func (p *PrivateKey) FromBytes(data []byte) error { 201 | return p.privateKey.FromBytes(data) 202 | } 203 | 204 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 205 | return p.privateKey.MarshalBinary() 206 | } 207 | 208 | func (p *PrivateKey) MarshalText() ([]byte, error) { 209 | return p.privateKey.MarshalText() 210 | } 211 | 212 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 213 | return p.privateKey.UnmarshalBinary(data) 214 | } 215 | 216 | func (p *PrivateKey) UnmarshalText(data []byte) error { 217 | return p.privateKey.UnmarshalText(data) 218 | } 219 | -------------------------------------------------------------------------------- /kem/schemes/schemes.go: -------------------------------------------------------------------------------- 1 | package schemes 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/katzenpost/circl/kem/frodo/frodo640shake" 7 | "github.com/katzenpost/circl/kem/kyber/kyber768" 8 | "github.com/katzenpost/circl/kem/mceliece/mceliece348864" 9 | "github.com/katzenpost/circl/kem/mceliece/mceliece348864f" 10 | "github.com/katzenpost/circl/kem/mceliece/mceliece460896" 11 | "github.com/katzenpost/circl/kem/mceliece/mceliece460896f" 12 | "github.com/katzenpost/circl/kem/mceliece/mceliece6688128" 13 | "github.com/katzenpost/circl/kem/mceliece/mceliece6688128f" 14 | "github.com/katzenpost/circl/kem/mceliece/mceliece6960119" 15 | "github.com/katzenpost/circl/kem/mceliece/mceliece6960119f" 16 | "github.com/katzenpost/circl/kem/mceliece/mceliece8192128" 17 | "github.com/katzenpost/circl/kem/mceliece/mceliece8192128f" 18 | 19 | "github.com/katzenpost/hpqc/kem" 20 | "github.com/katzenpost/hpqc/kem/adapter" 21 | "github.com/katzenpost/hpqc/kem/combiner" 22 | "github.com/katzenpost/hpqc/kem/hybrid" 23 | "github.com/katzenpost/hpqc/kem/mlkem768" 24 | "github.com/katzenpost/hpqc/kem/sntrup" 25 | "github.com/katzenpost/hpqc/kem/xwing" 26 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh1024" 27 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh2048" 28 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh511" 29 | "github.com/katzenpost/hpqc/nike/ctidh/ctidh512" 30 | "github.com/katzenpost/hpqc/nike/x25519" 31 | "github.com/katzenpost/hpqc/nike/x448" 32 | "github.com/katzenpost/hpqc/rand" 33 | ) 34 | 35 | var potentialSchemes = [...]kem.Scheme{ 36 | 37 | // PQ KEMs 38 | 39 | adapter.FromNIKE(ctidh511.Scheme()), 40 | adapter.FromNIKE(ctidh512.Scheme()), 41 | adapter.FromNIKE(ctidh1024.Scheme()), 42 | adapter.FromNIKE(ctidh2048.Scheme()), 43 | 44 | // hybrid KEMs 45 | 46 | combiner.New( 47 | "CTIDH512-X25519", 48 | []kem.Scheme{ 49 | adapter.FromNIKE(ctidh512.Scheme()), 50 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 51 | }, 52 | ), 53 | combiner.New( 54 | "CTIDH1024-X448", 55 | []kem.Scheme{ 56 | adapter.FromNIKE(ctidh1024.Scheme()), 57 | adapter.FromNIKE(x448.Scheme(rand.Reader)), 58 | }, 59 | ), 60 | } 61 | 62 | var allSchemes = []kem.Scheme{ 63 | 64 | // classical KEM schemes (converted from NIKE via hashed elgamal construction) 65 | 66 | // Classical DiffieHellman imeplementation has a bug with this ticket: 67 | // https://github.com/katzenpost/hpqc/issues/39 68 | //adapter.FromNIKE(diffiehellman.Scheme()), 69 | 70 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 71 | adapter.FromNIKE(x448.Scheme(rand.Reader)), 72 | 73 | // post quantum KEM schemes 74 | 75 | mlkem768.Scheme(), 76 | sntrup.Scheme(), 77 | frodo640shake.Scheme(), 78 | mceliece348864.Scheme(), 79 | mceliece348864f.Scheme(), 80 | mceliece460896.Scheme(), 81 | mceliece460896f.Scheme(), 82 | mceliece6688128.Scheme(), 83 | mceliece6688128f.Scheme(), 84 | mceliece6960119.Scheme(), 85 | mceliece6960119f.Scheme(), 86 | mceliece8192128.Scheme(), 87 | mceliece8192128f.Scheme(), 88 | 89 | // hybrid KEM schemes 90 | 91 | xwing.Scheme(), 92 | 93 | // XXX TODO: must soon deprecate use of "hybrid.New" in favour of "combiner.New". 94 | // We'd also like to remove Kyber now that we have mlkem768. 95 | hybrid.New( 96 | "Kyber768-X25519", 97 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 98 | kyber768.Scheme(), 99 | ), 100 | 101 | // If Xwing is not the PQ Hybrid KEM you are looking for then we recommend 102 | // using our secure generic KEM combiner: 103 | combiner.New( 104 | "MLKEM768-X25519", 105 | []kem.Scheme{ 106 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 107 | mlkem768.Scheme(), 108 | }, 109 | ), 110 | combiner.New( 111 | "MLKEM768-X448", 112 | []kem.Scheme{ 113 | adapter.FromNIKE(x448.Scheme(rand.Reader)), 114 | mlkem768.Scheme(), 115 | }, 116 | ), 117 | 118 | combiner.New( 119 | "FrodoKEM-640-SHAKE-X448", 120 | []kem.Scheme{ 121 | adapter.FromNIKE(x448.Scheme(rand.Reader)), 122 | frodo640shake.Scheme(), 123 | }, 124 | ), 125 | combiner.New( 126 | "sntrup4591761-X448", 127 | []kem.Scheme{ 128 | adapter.FromNIKE(x448.Scheme(rand.Reader)), 129 | sntrup.Scheme(), 130 | }, 131 | ), 132 | 133 | // all the Classic McEliece's from our fork of circl 134 | combiner.New( 135 | "mceliece348864-X25519", 136 | []kem.Scheme{ 137 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 138 | mceliece348864.Scheme(), 139 | }, 140 | ), 141 | combiner.New( 142 | "mceliece348864f-X25519", 143 | []kem.Scheme{ 144 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 145 | mceliece348864f.Scheme(), 146 | }, 147 | ), 148 | combiner.New( 149 | "mceliece460896-X25519", 150 | []kem.Scheme{ 151 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 152 | mceliece460896.Scheme(), 153 | }, 154 | ), 155 | combiner.New( 156 | "mceliece460896f-X25519", 157 | []kem.Scheme{ 158 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 159 | mceliece460896f.Scheme(), 160 | }, 161 | ), 162 | combiner.New( 163 | "mceliece6688128-X25519", 164 | []kem.Scheme{ 165 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 166 | mceliece6688128.Scheme(), 167 | }, 168 | ), 169 | combiner.New( 170 | "mceliece6688128f-X25519", 171 | []kem.Scheme{ 172 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 173 | mceliece6688128f.Scheme(), 174 | }, 175 | ), 176 | combiner.New( 177 | "mceliece6960119-X25519", 178 | []kem.Scheme{ 179 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 180 | mceliece6960119.Scheme(), 181 | }, 182 | ), 183 | combiner.New( 184 | "mceliece6960119f-X25519", 185 | []kem.Scheme{ 186 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 187 | mceliece6960119f.Scheme(), 188 | }, 189 | ), 190 | combiner.New( 191 | "mceliece8192128-X25519", 192 | []kem.Scheme{ 193 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 194 | mceliece8192128.Scheme(), 195 | }, 196 | ), 197 | combiner.New( 198 | "mceliece8192128f-X25519", 199 | []kem.Scheme{ 200 | adapter.FromNIKE(x25519.Scheme(rand.Reader)), 201 | mceliece8192128f.Scheme(), 202 | }, 203 | ), 204 | } 205 | 206 | var allSchemeNames map[string]kem.Scheme 207 | 208 | func init() { 209 | allSchemeNames = make(map[string]kem.Scheme) 210 | for _, scheme := range potentialSchemes { 211 | if scheme != nil { 212 | allSchemes = append(allSchemes, scheme) 213 | } 214 | } 215 | for _, scheme := range allSchemes { 216 | allSchemeNames[strings.ToLower(scheme.Name())] = scheme 217 | } 218 | } 219 | 220 | // ByName returns the NIKE scheme by string name. 221 | func ByName(name string) kem.Scheme { 222 | ret := allSchemeNames[strings.ToLower(name)] 223 | return ret 224 | } 225 | 226 | // All returns all NIKE schemes supported. 227 | func All() []kem.Scheme { 228 | a := allSchemes 229 | return a[:] 230 | } 231 | -------------------------------------------------------------------------------- /sign/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Cloudflare. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Cloudflare nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | ======================================================================== 31 | 32 | Copyright (c) 2009 The Go Authors. All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are 36 | met: 37 | 38 | * Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following disclaimer 42 | in the documentation and/or other materials provided with the 43 | distribution. 44 | * Neither the name of Google Inc. nor the names of its 45 | contributors may be used to endorse or promote products derived from 46 | this software without specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 49 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 50 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 51 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 52 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | 60 | */ 61 | 62 | package sign 63 | 64 | import ( 65 | "crypto" 66 | "encoding" 67 | "errors" 68 | ) 69 | 70 | type SignatureOpts struct { 71 | // If non-empty, includes the given context in the signature if supported 72 | // and will cause an error during signing otherwise. 73 | Context string 74 | } 75 | 76 | // A public key is used to verify a signature set by the corresponding private 77 | // key. 78 | type PublicKey interface { 79 | encoding.BinaryMarshaler 80 | encoding.TextMarshaler 81 | crypto.PublicKey 82 | 83 | // Returns the signature scheme for this public key. 84 | Scheme() Scheme 85 | Equal(crypto.PublicKey) bool 86 | } 87 | 88 | // A private key allows one to create signatures. 89 | type PrivateKey interface { 90 | crypto.Signer 91 | crypto.PrivateKey 92 | encoding.BinaryMarshaler 93 | encoding.BinaryUnmarshaler 94 | 95 | // Returns the signature scheme for this private key. 96 | Scheme() Scheme 97 | Equal(crypto.PrivateKey) bool 98 | } 99 | 100 | // A Scheme represents a specific instance of a signature scheme. 101 | type Scheme interface { 102 | // Name of the scheme. 103 | Name() string 104 | 105 | // GenerateKey creates a new key-pair. 106 | GenerateKey() (PublicKey, PrivateKey, error) 107 | 108 | // Creates a signature using the PrivateKey on the given message and 109 | // returns the signature. opts are additional options which can be nil. 110 | // 111 | // Panics if key is nil or wrong type or opts context is not supported. 112 | Sign(sk PrivateKey, message []byte, opts *SignatureOpts) []byte 113 | 114 | // Checks whether the given signature is a valid signature set by 115 | // the private key corresponding to the given public key on the 116 | // given message. opts are additional options which can be nil. 117 | // 118 | // Panics if key is nil or wrong type or opts context is not supported. 119 | Verify(pk PublicKey, message []byte, signature []byte, opts *SignatureOpts) bool 120 | 121 | // Deterministically derives a keypair from a seed. If you're unsure, 122 | // you're better off using GenerateKey(). 123 | // 124 | // Panics if seed is not of length SeedSize(). 125 | DeriveKey(seed []byte) (PublicKey, PrivateKey) 126 | 127 | // Unmarshals a PublicKey from the provided buffer. 128 | UnmarshalBinaryPublicKey([]byte) (PublicKey, error) 129 | 130 | // Unmarshals a PublicKey from the provided buffer. 131 | UnmarshalBinaryPrivateKey([]byte) (PrivateKey, error) 132 | 133 | // Size of binary marshalled public keys. 134 | PublicKeySize() int 135 | 136 | // Size of binary marshalled public keys. 137 | PrivateKeySize() int 138 | 139 | // Size of signatures. 140 | SignatureSize() int 141 | 142 | // Size of seeds. 143 | SeedSize() int 144 | 145 | // Returns whether contexts are supported. 146 | SupportsContext() bool 147 | } 148 | 149 | var ( 150 | // ErrTypeMismatch is the error used if types of, for instance, private 151 | // and public keys don't match. 152 | ErrTypeMismatch = errors.New("types mismatch") 153 | 154 | // ErrSeedSize is the error used if the provided seed is of the wrong 155 | // size. 156 | ErrSeedSize = errors.New("wrong seed size") 157 | 158 | // ErrPubKeySize is the error used if the provided public key is of 159 | // the wrong size. 160 | ErrPubKeySize = errors.New("wrong size for public key") 161 | 162 | // ErrPrivKeySize is the error used if the provided private key is of 163 | // the wrong size. 164 | ErrPrivKeySize = errors.New("wrong size for private key") 165 | 166 | // ErrContextNotSupported is the error used if a context is not 167 | // supported. 168 | ErrContextNotSupported = errors.New("context not supported") 169 | ) 170 | -------------------------------------------------------------------------------- /kem/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Cloudflare. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Cloudflare nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | ======================================================================== 31 | 32 | Copyright (c) 2009 The Go Authors. All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are 36 | met: 37 | 38 | * Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following disclaimer 42 | in the documentation and/or other materials provided with the 43 | distribution. 44 | * Neither the name of Google Inc. nor the names of its 45 | contributors may be used to endorse or promote products derived from 46 | this software without specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 49 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 50 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 51 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 52 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | 60 | */ 61 | 62 | // Package kem provides a unified interface for KEM schemes. 63 | // 64 | // A register of schemes is available in the package 65 | // 66 | // github.com/katzenpost/hpqc/kem/schemes 67 | package kem 68 | 69 | import ( 70 | "encoding" 71 | "errors" 72 | ) 73 | 74 | // A KEM public key 75 | type PublicKey interface { 76 | encoding.TextMarshaler 77 | encoding.BinaryMarshaler 78 | 79 | // Returns the scheme for this public key 80 | Scheme() Scheme 81 | 82 | Equal(PublicKey) bool 83 | } 84 | 85 | // A KEM private key 86 | type PrivateKey interface { 87 | encoding.BinaryMarshaler 88 | 89 | // Returns the scheme for this private key 90 | Scheme() Scheme 91 | 92 | // Equal returns true if the two keys are equal. 93 | Equal(PrivateKey) bool 94 | 95 | // Public returns the public key related to this private key. 96 | Public() PublicKey 97 | } 98 | 99 | // A Scheme represents a specific instance of a KEM. 100 | type Scheme interface { 101 | // Name of the scheme 102 | Name() string 103 | 104 | // GenerateKeyPair creates a new key pair. 105 | GenerateKeyPair() (PublicKey, PrivateKey, error) 106 | 107 | // Encapsulate generates a shared key ss for the public key and 108 | // encapsulates it into a ciphertext ct. 109 | Encapsulate(pk PublicKey) (ct, ss []byte, err error) 110 | 111 | // Returns the shared key encapsulated in ciphertext ct for the 112 | // private key sk. 113 | Decapsulate(sk PrivateKey, ct []byte) ([]byte, error) 114 | 115 | // UnmarshalBinaryPublicKey unmarshals a PublicKey from the provided buffer. 116 | UnmarshalBinaryPublicKey([]byte) (PublicKey, error) 117 | 118 | // UnmarshalBinaryPrivateKey unmarshals a PrivateKey from the provided buffer. 119 | UnmarshalBinaryPrivateKey([]byte) (PrivateKey, error) 120 | 121 | // UnmarshalTextPublicKey unmarshals a PublicKey from the provided text. 122 | UnmarshalTextPublicKey([]byte) (PublicKey, error) 123 | 124 | // UnmarshalTextPrivateKey unmarshals a PrivateKey from the provided text. 125 | UnmarshalTextPrivateKey([]byte) (PrivateKey, error) 126 | 127 | // Size of encapsulated keys. 128 | CiphertextSize() int 129 | 130 | // Size of established shared keys. 131 | SharedKeySize() int 132 | 133 | // Size of packed private keys. 134 | PrivateKeySize() int 135 | 136 | // Size of packed public keys. 137 | PublicKeySize() int 138 | 139 | // DeriveKeyPair deterministicallly derives a pair of keys from a seed. 140 | // Panics if the length of seed is not equal to the value returned by 141 | // SeedSize. 142 | DeriveKeyPair(seed []byte) (PublicKey, PrivateKey) 143 | 144 | // Size of seed used in DeriveKey 145 | SeedSize() int 146 | } 147 | 148 | var ( 149 | // ErrTypeMismatch is the error used if types of, for instance, private 150 | // and public keys don't match 151 | ErrTypeMismatch = errors.New("types mismatch") 152 | 153 | // ErrSeedSize is the error used if the provided seed is of the wrong 154 | // size. 155 | ErrSeedSize = errors.New("wrong seed size") 156 | 157 | // ErrPubKeySize is the error used if the provided public key is of 158 | // the wrong size. 159 | ErrPubKeySize = errors.New("wrong size for public key") 160 | 161 | // ErrCiphertextSize is the error used if the provided ciphertext 162 | // is of the wrong size. 163 | ErrCiphertextSize = errors.New("wrong size for ciphertext") 164 | 165 | // ErrPrivKeySize is the error used if the provided private key is of 166 | // the wrong size. 167 | ErrPrivKeySize = errors.New("wrong size for private key") 168 | 169 | // ErrPubKey is the error used if the provided public key is invalid. 170 | ErrPubKey = errors.New("invalid public key") 171 | 172 | // ErrCipherText is the error used if the provided ciphertext is invalid. 173 | ErrCipherText = errors.New("invalid ciphertext") 174 | ) 175 | -------------------------------------------------------------------------------- /sign/schemes/schemes_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Cloudflare. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Cloudflare nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | ======================================================================== 31 | 32 | Copyright (c) 2009 The Go Authors. All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are 36 | met: 37 | 38 | * Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following disclaimer 42 | in the documentation and/or other materials provided with the 43 | distribution. 44 | * Neither the name of Google Inc. nor the names of its 45 | contributors may be used to endorse or promote products derived from 46 | this software without specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 49 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 50 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 51 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 52 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | 60 | */ 61 | 62 | package schemes_test 63 | 64 | import ( 65 | "fmt" 66 | "testing" 67 | 68 | "github.com/katzenpost/hpqc/sign" 69 | "github.com/katzenpost/hpqc/sign/schemes" 70 | ) 71 | 72 | func TestCaseSensitivity(t *testing.T) { 73 | if schemes.ByName("ed25519") != schemes.ByName("Ed25519") { 74 | t.Fatal() 75 | } 76 | } 77 | 78 | func TestApi(t *testing.T) { 79 | allSchemes := schemes.All() 80 | for _, scheme := range allSchemes { 81 | scheme := scheme 82 | t.Run(scheme.Name(), func(t *testing.T) { 83 | if scheme == nil { 84 | t.Fatal() 85 | } 86 | 87 | t.Logf("%s Signature size is %d\n", scheme.Name(), scheme.SignatureSize()) 88 | t.Logf("%s Public Key size is %d\n", scheme.Name(), scheme.PublicKeySize()) 89 | 90 | pk, sk, err := scheme.GenerateKey() 91 | if err != nil { 92 | t.Fatal() 93 | } 94 | 95 | packedPk, err := pk.MarshalBinary() 96 | if err != nil { 97 | t.Fatal() 98 | } 99 | 100 | if len(packedPk) != scheme.PublicKeySize() { 101 | t.Fatal() 102 | } 103 | 104 | packedSk, err := sk.MarshalBinary() 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | if len(packedSk) != scheme.PrivateKeySize() { 110 | t.Fatal() 111 | } 112 | 113 | pk2, err := scheme.UnmarshalBinaryPublicKey(packedPk) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | sk2, err := scheme.UnmarshalBinaryPrivateKey(packedSk) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | 123 | if !sk.Equal(sk2) { 124 | t.Fatal() 125 | } 126 | 127 | if !pk.Equal(pk2) { 128 | t.Fatal() 129 | } 130 | 131 | msg := []byte(fmt.Sprintf("Signing with %s", scheme.Name())) 132 | opts := &sign.SignatureOpts{} 133 | if scheme.SupportsContext() { 134 | opts.Context = "A context" 135 | } 136 | sig := scheme.Sign(sk, msg, opts) 137 | 138 | if scheme.SignatureSize() != len(sig) { 139 | t.Fatal() 140 | } 141 | 142 | if !scheme.Verify(pk2, msg, sig, opts) { 143 | t.Fatal() 144 | } 145 | 146 | if scheme.SupportsContext() { 147 | opts2 := opts 148 | opts2.Context = "Wrong context" 149 | if scheme.Verify(pk2, msg, sig, opts2) { 150 | t.Fatal() 151 | } 152 | } 153 | 154 | sig[0]++ 155 | if scheme.Verify(pk2, msg, sig, opts) { 156 | t.Fatal() 157 | } 158 | 159 | scheme2 := schemes.ByName(scheme.Name()) 160 | if scheme2 == nil || scheme2 != scheme { 161 | t.Fatal() 162 | } 163 | 164 | if pk.Scheme() != scheme { 165 | t.Fatal() 166 | } 167 | 168 | if sk.Scheme() != scheme { 169 | t.Fatal() 170 | } 171 | }) 172 | } 173 | } 174 | 175 | func BenchmarkGenerateKeyPair(b *testing.B) { 176 | allSchemes := schemes.All() 177 | for _, scheme := range allSchemes { 178 | scheme := scheme 179 | b.Run(scheme.Name(), func(b *testing.B) { 180 | for i := 0; i < b.N; i++ { 181 | _, _, _ = scheme.GenerateKey() 182 | } 183 | }) 184 | } 185 | } 186 | 187 | func BenchmarkSign(b *testing.B) { 188 | allSchemes := schemes.All() 189 | opts := &sign.SignatureOpts{} 190 | for _, scheme := range allSchemes { 191 | msg := []byte(fmt.Sprintf("Signing with %s", scheme.Name())) 192 | scheme := scheme 193 | _, sk, _ := scheme.GenerateKey() 194 | b.Run(scheme.Name(), func(b *testing.B) { 195 | for i := 0; i < b.N; i++ { 196 | _ = scheme.Sign(sk, msg, opts) 197 | } 198 | }) 199 | } 200 | } 201 | 202 | func BenchmarkVerify(b *testing.B) { 203 | allSchemes := schemes.All() 204 | opts := &sign.SignatureOpts{} 205 | for _, scheme := range allSchemes { 206 | msg := []byte(fmt.Sprintf("Signing with %s", scheme.Name())) 207 | scheme := scheme 208 | pk, sk, _ := scheme.GenerateKey() 209 | sig := scheme.Sign(sk, msg, opts) 210 | b.Run(scheme.Name(), func(b *testing.B) { 211 | for i := 0; i < b.N; i++ { 212 | _ = scheme.Verify(pk, msg, sig, opts) 213 | } 214 | }) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /sign/hybrid/hybrid.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package hybrid 5 | 6 | import ( 7 | "crypto" 8 | "crypto/hmac" 9 | "io" 10 | 11 | "github.com/katzenpost/hpqc/sign" 12 | "github.com/katzenpost/hpqc/sign/pem" 13 | ) 14 | 15 | // Scheme is for hybrid signature schemes. 16 | type Scheme struct { 17 | name string 18 | first sign.Scheme 19 | second sign.Scheme 20 | } 21 | 22 | var _ sign.Scheme = (*Scheme)(nil) 23 | var _ sign.PrivateKey = (*PrivateKey)(nil) 24 | var _ sign.PublicKey = (*PublicKey)(nil) 25 | 26 | // New creates a new hybrid signature scheme given the two signature schemes, 27 | // assumign one of them is classical and the other post quantum. 28 | func New(name string, first sign.Scheme, second sign.Scheme) sign.Scheme { 29 | return &Scheme{ 30 | name: name, 31 | first: first, 32 | second: second, 33 | } 34 | } 35 | 36 | func (s *Scheme) Name() string { 37 | return s.name 38 | } 39 | 40 | func (s *Scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { 41 | pub1, priv1, err := s.first.GenerateKey() 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | pub2, priv2, err := s.second.GenerateKey() 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | return &PublicKey{ 50 | scheme: s, 51 | first: pub1, 52 | second: pub2, 53 | }, &PrivateKey{ 54 | scheme: s, 55 | first: priv1, 56 | second: priv2, 57 | }, nil 58 | } 59 | 60 | func (s *Scheme) Sign(sk sign.PrivateKey, message []byte, opts *sign.SignatureOpts) []byte { 61 | return append(s.first.Sign(sk.(*PrivateKey).first, message, opts), 62 | s.second.Sign(sk.(*PrivateKey).second, message, opts)...) 63 | } 64 | 65 | func (s *Scheme) Verify(pk sign.PublicKey, message []byte, signature []byte, opts *sign.SignatureOpts) bool { 66 | if len(signature) != s.SignatureSize() { 67 | panic("incorrect signature size") 68 | } 69 | if !s.first.Verify(pk.(*PublicKey).first, message, signature[:s.first.SignatureSize()], opts) { 70 | return false 71 | } 72 | if !s.second.Verify(pk.(*PublicKey).second, message, signature[s.first.SignatureSize():], opts) { 73 | return false 74 | } 75 | return true 76 | } 77 | 78 | func (s *Scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { 79 | if len(seed) != s.SeedSize() { 80 | panic("wrong seed size") 81 | } 82 | pub1, priv1 := s.first.DeriveKey(seed[:s.first.SeedSize()]) 83 | pub2, priv2 := s.second.DeriveKey(seed[s.first.SeedSize():]) 84 | return &PublicKey{ 85 | scheme: s, 86 | first: pub1, 87 | second: pub2, 88 | }, &PrivateKey{ 89 | scheme: s, 90 | first: priv1, 91 | second: priv2, 92 | } 93 | } 94 | 95 | func (s *Scheme) UnmarshalBinaryPublicKey(b []byte) (sign.PublicKey, error) { 96 | pub1, err := s.first.UnmarshalBinaryPublicKey(b[:s.first.PublicKeySize()]) 97 | if err != nil { 98 | return nil, err 99 | } 100 | pub2, err := s.second.UnmarshalBinaryPublicKey(b[s.first.PublicKeySize():]) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return &PublicKey{ 105 | scheme: s, 106 | first: pub1, 107 | second: pub2, 108 | }, nil 109 | } 110 | 111 | func (s *Scheme) UnmarshalBinaryPrivateKey(b []byte) (sign.PrivateKey, error) { 112 | priv1, err := s.first.UnmarshalBinaryPrivateKey(b[:s.first.PrivateKeySize()]) 113 | if err != nil { 114 | return nil, err 115 | } 116 | priv2, err := s.second.UnmarshalBinaryPrivateKey(b[s.first.PrivateKeySize():]) 117 | if err != nil { 118 | return nil, err 119 | } 120 | return &PrivateKey{ 121 | scheme: s, 122 | first: priv1, 123 | second: priv2, 124 | }, nil 125 | } 126 | 127 | func (s *Scheme) PublicKeySize() int { 128 | return s.first.PublicKeySize() + s.second.PublicKeySize() 129 | } 130 | 131 | func (s *Scheme) PrivateKeySize() int { 132 | return s.first.PrivateKeySize() + s.second.PrivateKeySize() 133 | } 134 | 135 | func (s *Scheme) SignatureSize() int { 136 | return s.first.SignatureSize() + s.second.SignatureSize() 137 | } 138 | 139 | func (s *Scheme) SeedSize() int { 140 | return s.first.SeedSize() + s.second.SeedSize() 141 | } 142 | 143 | func (s *Scheme) SupportsContext() bool { 144 | if !s.first.SupportsContext() { 145 | return false 146 | } 147 | if !s.second.SupportsContext() { 148 | return false 149 | } 150 | return true 151 | } 152 | 153 | // PrivateKey is the private key in hybrid signature scheme. 154 | type PrivateKey struct { 155 | scheme *Scheme 156 | publicKey *PublicKey 157 | first sign.PrivateKey 158 | second sign.PrivateKey 159 | } 160 | 161 | func (p *PrivateKey) Scheme() sign.Scheme { 162 | return p.scheme 163 | } 164 | 165 | func (p *PrivateKey) Equal(key crypto.PrivateKey) bool { 166 | blob1, err := p.MarshalBinary() 167 | if err != nil { 168 | panic(err) 169 | } 170 | blob2, err := key.(*PrivateKey).MarshalBinary() 171 | if err != nil { 172 | panic(err) 173 | } 174 | return hmac.Equal(blob1, blob2) 175 | } 176 | 177 | func (p *PrivateKey) Public() crypto.PublicKey { 178 | panic("not implemented") 179 | } 180 | 181 | func (p *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 182 | sig1, err := p.first.Sign(rand, digest, opts) 183 | if err != nil { 184 | return nil, err 185 | } 186 | sig2, err := p.second.Sign(rand, digest, opts) 187 | if err != nil { 188 | return nil, err 189 | } 190 | return append(sig1, sig2...), nil 191 | } 192 | 193 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 194 | blob1, err := p.first.MarshalBinary() 195 | if err != nil { 196 | return nil, err 197 | } 198 | blob2, err := p.second.MarshalBinary() 199 | if err != nil { 200 | return nil, err 201 | } 202 | return append(blob1, blob2...), nil 203 | } 204 | 205 | func (p *PrivateKey) UnmarshalBinary(b []byte) error { 206 | err := p.first.UnmarshalBinary(b[:p.first.Scheme().PrivateKeySize()]) 207 | if err != nil { 208 | return err 209 | } 210 | return p.second.UnmarshalBinary(b[p.first.Scheme().PrivateKeySize():]) 211 | } 212 | 213 | // PublicKey is the public key in hybrid signature scheme. 214 | type PublicKey struct { 215 | scheme *Scheme 216 | first sign.PublicKey 217 | second sign.PublicKey 218 | } 219 | 220 | func (p *PublicKey) Scheme() sign.Scheme { 221 | return p.scheme 222 | } 223 | 224 | func (p *PublicKey) Equal(key crypto.PublicKey) bool { 225 | blob1, err := p.MarshalBinary() 226 | if err != nil { 227 | panic(err) 228 | } 229 | var blob2 []byte 230 | switch v := key.(type) { 231 | case []byte: 232 | blob2 = v 233 | case *PublicKey: 234 | blob2, err = v.MarshalBinary() 235 | if err != nil { 236 | panic(err) 237 | } 238 | default: 239 | panic("type assertion failed") 240 | } 241 | return hmac.Equal(blob1, blob2) 242 | } 243 | 244 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 245 | blob1, err := p.first.MarshalBinary() 246 | if err != nil { 247 | return nil, err 248 | } 249 | blob2, err := p.second.MarshalBinary() 250 | if err != nil { 251 | return nil, err 252 | } 253 | return append(blob1, blob2...), nil 254 | } 255 | 256 | func (p *PublicKey) MarshalText() (text []byte, err error) { 257 | return pem.ToPublicPEMBytes(p), nil 258 | } 259 | -------------------------------------------------------------------------------- /kem/hybrid/hybrid.go: -------------------------------------------------------------------------------- 1 | // Package hybrid defines several hybrid classical/quantum KEMs. 2 | // 3 | // KEMs are combined by hashing of shared secrets, cipher texts, 4 | // public keys, etc, see 5 | // 6 | // https://eprint.iacr.org/2018/024.pdf 7 | // 8 | // For deriving a KEM keypair deterministically and encapsulating 9 | // deterministically, we expand a single seed to both using Blake2b hash and then XOF, 10 | // so that a non-uniform seed (such as a shared secret generated by a hybrid 11 | // KEM where one of the KEMs is weak) doesn't impact just one of the KEMs. 12 | 13 | package hybrid 14 | 15 | import ( 16 | "errors" 17 | "fmt" 18 | 19 | "github.com/katzenpost/hpqc/kem" 20 | "github.com/katzenpost/hpqc/kem/pem" 21 | "github.com/katzenpost/hpqc/kem/util" 22 | "golang.org/x/crypto/blake2b" 23 | ) 24 | 25 | var ( 26 | ErrUninitialized = errors.New("public or private key not initialized") 27 | ) 28 | 29 | var _ kem.PrivateKey = (*PrivateKey)(nil) 30 | var _ kem.PublicKey = (*PublicKey)(nil) 31 | var _ kem.Scheme = (*Scheme)(nil) 32 | 33 | // Public key of a hybrid KEM. 34 | type PublicKey struct { 35 | scheme *Scheme 36 | first kem.PublicKey 37 | second kem.PublicKey 38 | } 39 | 40 | // Private key of a hybrid KEM. 41 | type PrivateKey struct { 42 | scheme *Scheme 43 | first kem.PrivateKey 44 | second kem.PrivateKey 45 | } 46 | 47 | // Scheme for a hybrid KEM. 48 | type Scheme struct { 49 | name string 50 | first kem.Scheme 51 | second kem.Scheme 52 | } 53 | 54 | // New creates a new hybrid KEM given the first and second KEMs. 55 | func New(name string, first kem.Scheme, second kem.Scheme) *Scheme { 56 | return &Scheme{ 57 | name: name, 58 | first: first, 59 | second: second, 60 | } 61 | } 62 | 63 | func (sch *Scheme) Name() string { return sch.name } 64 | func (sch *Scheme) PublicKeySize() int { 65 | return sch.first.PublicKeySize() + sch.second.PublicKeySize() 66 | } 67 | 68 | func (sch *Scheme) PrivateKeySize() int { 69 | return sch.first.PrivateKeySize() + sch.second.PrivateKeySize() 70 | } 71 | 72 | func (sch *Scheme) SeedSize() int { 73 | return sch.first.SeedSize() + sch.second.SeedSize() 74 | } 75 | 76 | func (sch *Scheme) SharedKeySize() int { 77 | return blake2b.Size256 78 | } 79 | 80 | func (sch *Scheme) CiphertextSize() int { 81 | return sch.first.CiphertextSize() + sch.second.CiphertextSize() 82 | } 83 | 84 | func (sk *PrivateKey) Scheme() kem.Scheme { return sk.scheme } 85 | func (pk *PublicKey) Scheme() kem.Scheme { return pk.scheme } 86 | 87 | func (sk *PrivateKey) MarshalBinary() ([]byte, error) { 88 | if sk.first == nil || sk.second == nil { 89 | return nil, ErrUninitialized 90 | } 91 | first, err := sk.first.MarshalBinary() 92 | if err != nil { 93 | return nil, err 94 | } 95 | second, err := sk.second.MarshalBinary() 96 | if err != nil { 97 | return nil, err 98 | } 99 | return append(first, second...), nil 100 | } 101 | 102 | func (sk *PublicKey) MarshalText() (text []byte, err error) { 103 | return pem.ToPublicPEMBytes(sk), nil 104 | } 105 | 106 | func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { 107 | oth, ok := other.(*PrivateKey) 108 | if !ok { 109 | return false 110 | } 111 | return sk.first.Equal(oth.first) && sk.second.Equal(oth.second) 112 | } 113 | 114 | func (sk *PrivateKey) Public() kem.PublicKey { 115 | return &PublicKey{sk.scheme, sk.first.Public(), sk.second.Public()} 116 | } 117 | 118 | func (pk *PublicKey) Equal(other kem.PublicKey) bool { 119 | oth, ok := other.(*PublicKey) 120 | if !ok { 121 | return false 122 | } 123 | return pk.first.Equal(oth.first) && pk.second.Equal(oth.second) 124 | } 125 | 126 | func (pk *PublicKey) MarshalBinary() ([]byte, error) { 127 | if pk.first == nil || pk.second == nil { 128 | return nil, ErrUninitialized 129 | } 130 | first, err := pk.first.MarshalBinary() 131 | if err != nil { 132 | return nil, err 133 | } 134 | second, err := pk.second.MarshalBinary() 135 | if err != nil { 136 | return nil, err 137 | } 138 | return append(first, second...), nil 139 | } 140 | 141 | func (sch *Scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { 142 | pk1, sk1, err := sch.first.GenerateKeyPair() 143 | if err != nil { 144 | return nil, nil, err 145 | } 146 | pk2, sk2, err := sch.second.GenerateKeyPair() 147 | if err != nil { 148 | return nil, nil, err 149 | } 150 | 151 | return &PublicKey{sch, pk1, pk2}, &PrivateKey{sch, sk1, sk2}, nil 152 | } 153 | 154 | func (sch *Scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { 155 | if len(seed) != sch.first.SeedSize()+sch.second.SeedSize() { 156 | panic(fmt.Sprintf("seed size must be %d", sch.first.SeedSize()+sch.second.SeedSize())) 157 | } 158 | 159 | pk1, sk1 := sch.first.DeriveKeyPair(seed[:sch.first.SeedSize()]) 160 | pk2, sk2 := sch.second.DeriveKeyPair(seed[sch.first.SeedSize():]) 161 | 162 | return &PublicKey{sch, pk1, pk2}, &PrivateKey{sch, sk1, sk2} 163 | } 164 | 165 | func (sch *Scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { 166 | pub, ok := pk.(*PublicKey) 167 | if !ok { 168 | return nil, nil, kem.ErrTypeMismatch 169 | } 170 | 171 | ct1, ss1, err := sch.first.Encapsulate(pub.first) 172 | if err != nil { 173 | return nil, nil, err 174 | } 175 | 176 | ct2, ss2, err := sch.second.Encapsulate(pub.second) 177 | if err != nil { 178 | return nil, nil, err 179 | } 180 | 181 | return append(ct1, ct2...), util.PairSplitPRF(ss1, ss2, ct1, ct2), nil 182 | } 183 | 184 | func (sch *Scheme) EncapsulateDeterministically(publicKey kem.PublicKey, seed []byte) (ct, ss []byte, err error) { 185 | panic("not implemented") 186 | } 187 | 188 | func (sch *Scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { 189 | if len(ct) != sch.CiphertextSize() { 190 | return nil, kem.ErrCiphertextSize 191 | } 192 | 193 | priv, ok := sk.(*PrivateKey) 194 | if !ok { 195 | return nil, kem.ErrTypeMismatch 196 | } 197 | 198 | firstSize := sch.first.CiphertextSize() 199 | ss1, err := sch.first.Decapsulate(priv.first, ct[:firstSize]) 200 | if err != nil { 201 | return nil, err 202 | } 203 | ss2, err := sch.second.Decapsulate(priv.second, ct[firstSize:]) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | return util.PairSplitPRF(ss1, ss2, ct[:firstSize], ct[firstSize:]), nil 209 | } 210 | 211 | func (sch *Scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { 212 | if len(buf) != sch.PublicKeySize() { 213 | return nil, kem.ErrPubKeySize 214 | } 215 | firstSize := sch.first.PublicKeySize() 216 | pk1, err := sch.first.UnmarshalBinaryPublicKey(buf[:firstSize]) 217 | if err != nil { 218 | return nil, err 219 | } 220 | pk2, err := sch.second.UnmarshalBinaryPublicKey(buf[firstSize:]) 221 | if err != nil { 222 | return nil, err 223 | } 224 | return &PublicKey{sch, pk1, pk2}, nil 225 | } 226 | 227 | func (sch *Scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { 228 | if len(buf) != sch.PrivateKeySize() { 229 | return nil, kem.ErrPrivKeySize 230 | } 231 | firstSize := sch.first.PrivateKeySize() 232 | sk1, err := sch.first.UnmarshalBinaryPrivateKey(buf[:firstSize]) 233 | if err != nil { 234 | return nil, err 235 | } 236 | sk2, err := sch.second.UnmarshalBinaryPrivateKey(buf[firstSize:]) 237 | if err != nil { 238 | return nil, err 239 | } 240 | return &PrivateKey{sch, sk1, sk2}, nil 241 | } 242 | 243 | func (a *Scheme) UnmarshalTextPublicKey(text []byte) (kem.PublicKey, error) { 244 | return pem.FromPublicPEMBytes(text, a) 245 | } 246 | 247 | func (a *Scheme) UnmarshalTextPrivateKey(text []byte) (kem.PrivateKey, error) { 248 | return pem.FromPrivatePEMBytes(text, a) 249 | } 250 | -------------------------------------------------------------------------------- /nike/csidh/csidh.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 || arm64 2 | 3 | // csidh.go - Adapts csidh module to our NIKE interface. 4 | // Copyright (C) 2022 David Stainton. 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU Affero General Public License as 8 | // published by the Free Software Foundation, either version 3 of the 9 | // License, or (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with this program. If not, see . 18 | 19 | package csidh 20 | 21 | import ( 22 | "encoding/base64" 23 | "errors" 24 | "io" 25 | 26 | "github.com/henrydcase/nobs/dh/csidh" 27 | 28 | "github.com/katzenpost/hpqc/nike" 29 | "github.com/katzenpost/hpqc/rand" 30 | ) 31 | 32 | // CSIDHScheme is the nobs CSIDH-512 NIKE. 33 | var NOBS_CSIDH512Scheme nike.Scheme 34 | 35 | var _ nike.PrivateKey = (*PrivateKey)(nil) 36 | var _ nike.PublicKey = (*PublicKey)(nil) 37 | var _ nike.Scheme = (*CsidhNike)(nil) 38 | 39 | type CsidhNike struct{} 40 | 41 | func (e *CsidhNike) Name() string { 42 | return "CSIDH-512-nobs" 43 | } 44 | 45 | func (e *CsidhNike) PublicKeySize() int { 46 | return csidh.PublicKeySize 47 | } 48 | 49 | func (e *CsidhNike) PrivateKeySize() int { 50 | return csidh.PrivateKeySize 51 | } 52 | 53 | func (e *CsidhNike) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 54 | privateKey := new(csidh.PrivateKey) 55 | err := csidh.GeneratePrivateKey(privateKey, rand.Reader) 56 | if err != nil { 57 | panic(err) 58 | } 59 | return &PrivateKey{ 60 | privateKey: privateKey, 61 | } 62 | } 63 | 64 | func (e *CsidhNike) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 65 | privateKey := new(csidh.PrivateKey) 66 | err := csidh.GeneratePrivateKey(privateKey, rng) 67 | if err != nil { 68 | return nil, nil, err 69 | } 70 | privKey := &PrivateKey{ 71 | privateKey: privateKey, 72 | } 73 | publicKey := e.DerivePublicKey(privKey) 74 | return publicKey, privKey, nil 75 | } 76 | 77 | func (e *CsidhNike) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 78 | privateKey := new(csidh.PrivateKey) 79 | err := csidh.GeneratePrivateKey(privateKey, rand.Reader) 80 | if err != nil { 81 | return nil, nil, err 82 | } 83 | privKey := &PrivateKey{ 84 | privateKey: privateKey, 85 | } 86 | publicKey := e.DerivePublicKey(privKey) 87 | return publicKey, privKey, nil 88 | } 89 | 90 | func (e *CsidhNike) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 91 | sharedSecret := &[64]byte{} 92 | ok := csidh.DeriveSecret(sharedSecret, pubKey.(*PublicKey).publicKey, privKey.(*PrivateKey).privateKey, rand.Reader) 93 | if !ok { 94 | panic("csidh.DeriveSecret failed!") 95 | } 96 | return sharedSecret[:] 97 | } 98 | 99 | func (e *CsidhNike) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 100 | pubKey := new(csidh.PublicKey) 101 | csidh.GeneratePublicKey(pubKey, privKey.(*PrivateKey).privateKey, rand.Reader) 102 | return &PublicKey{ 103 | publicKey: pubKey, 104 | } 105 | } 106 | 107 | func (e CsidhNike) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) (blindedGroupMember nike.PublicKey) { 108 | panic("Blind operation not implemented") 109 | } 110 | 111 | func (e *CsidhNike) NewEmptyPublicKey() nike.PublicKey { 112 | return &PublicKey{ 113 | publicKey: new(csidh.PublicKey), 114 | } 115 | } 116 | 117 | func (e *CsidhNike) NewEmptyPrivateKey() nike.PrivateKey { 118 | return &PrivateKey{ 119 | privateKey: new(csidh.PrivateKey), 120 | } 121 | } 122 | 123 | func (e *CsidhNike) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 124 | pubKey := new(csidh.PublicKey) 125 | ok := pubKey.Import(b) 126 | if !ok { 127 | return nil, errors.New("CSIDH public key import failure") 128 | } 129 | return &PublicKey{ 130 | publicKey: pubKey, 131 | }, nil 132 | } 133 | 134 | func (e *CsidhNike) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 135 | privKey := new(csidh.PrivateKey) 136 | ok := privKey.Import(b) 137 | if !ok { 138 | return nil, errors.New("CSIDH private key import failure") 139 | } 140 | return &PrivateKey{ 141 | privateKey: privKey, 142 | }, nil 143 | } 144 | 145 | type PublicKey struct { 146 | publicKey *csidh.PublicKey 147 | } 148 | 149 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 150 | panic("Blind operation not implemented") 151 | } 152 | 153 | func (p *PublicKey) Reset() { 154 | zeros := make([]byte, csidh.PublicKeySize) 155 | err := p.FromBytes(zeros) 156 | if err != nil { 157 | panic(err) 158 | } 159 | } 160 | 161 | func (p *PublicKey) Bytes() []byte { 162 | s := make([]byte, csidh.PublicKeySize) 163 | p.publicKey.Export(s) 164 | return s 165 | } 166 | 167 | func (p *PublicKey) FromBytes(b []byte) error { 168 | p.publicKey = new(csidh.PublicKey) 169 | ok := p.publicKey.Import(b) 170 | if !ok { 171 | return errors.New("csidh public key import failure") 172 | } 173 | return nil 174 | } 175 | 176 | // MarshalBinary is an implementation of a method on the 177 | // BinaryMarshaler interface defined in https://golang.org/pkg/encoding/ 178 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 179 | return p.Bytes(), nil 180 | } 181 | 182 | // UnmarshalBinary is an implementation of a method on the 183 | // BinaryUnmarshaler interface defined in https://golang.org/pkg/encoding/ 184 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 185 | return p.FromBytes(data) 186 | } 187 | 188 | // MarshalText is an implementation of a method on the 189 | // TextMarshaler interface defined in https://golang.org/pkg/encoding/ 190 | func (p *PublicKey) MarshalText() ([]byte, error) { 191 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 192 | } 193 | 194 | // UnmarshalText is an implementation of a method on the 195 | // TextUnmarshaler interface defined in https://golang.org/pkg/encoding/ 196 | func (p *PublicKey) UnmarshalText(data []byte) error { 197 | raw, err := base64.StdEncoding.DecodeString(string(data)) 198 | if err != nil { 199 | return err 200 | } 201 | return p.FromBytes(raw) 202 | } 203 | 204 | type PrivateKey struct { 205 | privateKey *csidh.PrivateKey 206 | } 207 | 208 | func (p *PrivateKey) Public() nike.PublicKey { 209 | return NOBS_CSIDH512Scheme.DerivePublicKey(p) 210 | } 211 | 212 | func (p *PrivateKey) Reset() { 213 | zeros := make([]byte, csidh.PrivateKeySize) 214 | err := p.FromBytes(zeros) 215 | if err != nil { 216 | panic(err) 217 | } 218 | } 219 | 220 | func (p *PrivateKey) Bytes() []byte { 221 | if p.privateKey == nil { 222 | panic("p.privateKey == nil") 223 | } 224 | s := make([]byte, csidh.PrivateKeySize) 225 | p.privateKey.Export(s) 226 | return s 227 | } 228 | 229 | func (p *PrivateKey) FromBytes(b []byte) error { 230 | p.privateKey = new(csidh.PrivateKey) 231 | ok := p.privateKey.Import(b) 232 | if !ok { 233 | return errors.New("csidh private key import failure") 234 | } 235 | return nil 236 | } 237 | 238 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 239 | s := make([]byte, csidh.PrivateKeySize) 240 | ok := p.privateKey.Export(s) 241 | if !ok { 242 | return nil, errors.New("MarshalBinary fail") 243 | } 244 | return s, nil 245 | } 246 | 247 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 248 | return p.FromBytes(data) 249 | } 250 | 251 | func (p *PrivateKey) MarshalText() ([]byte, error) { 252 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 253 | } 254 | 255 | func (p *PrivateKey) UnmarshalText(data []byte) error { 256 | raw, err := base64.StdEncoding.DecodeString(string(data)) 257 | if err != nil { 258 | return err 259 | } 260 | return p.FromBytes(raw) 261 | } 262 | 263 | func init() { 264 | NOBS_CSIDH512Scheme = new(CsidhNike) 265 | } 266 | -------------------------------------------------------------------------------- /nike/x448/x448.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2024 David Stainton 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package x448 5 | 6 | import ( 7 | "encoding/base64" 8 | "errors" 9 | "io" 10 | 11 | "github.com/katzenpost/circl/dh/x448" 12 | 13 | "github.com/katzenpost/hpqc/nike" 14 | "github.com/katzenpost/hpqc/rand" 15 | "github.com/katzenpost/hpqc/util" 16 | ) 17 | 18 | const ( 19 | // GroupElementLength is the length of a ECDH group element in bytes. 20 | GroupElementLength = 56 21 | 22 | // PublicKeySize is the size of a serialized PublicKey in bytes. 23 | PublicKeySize = GroupElementLength 24 | 25 | // PrivateKeySize is the size of a serialized PrivateKey in bytes. 26 | PrivateKeySize = GroupElementLength 27 | ) 28 | 29 | var ( 30 | // ErrBlindDataSizeInvalid indicates that the blinding data size was invalid. 31 | ErrBlindDataSizeInvalid error = errors.New("x448: blinding data size invalid") 32 | 33 | errInvalidKey = errors.New("x448: invalid key") 34 | ) 35 | 36 | var _ nike.PrivateKey = (*PrivateKey)(nil) 37 | var _ nike.PublicKey = (*PublicKey)(nil) 38 | var _ nike.Scheme = (*scheme)(nil) 39 | 40 | // EcdhNike implements the Nike interface using our ecdh module. 41 | type scheme struct { 42 | rng io.Reader 43 | } 44 | 45 | // Scheme instantiates a new X448 scheme given a CSPRNG. 46 | func Scheme(rng io.Reader) *scheme { 47 | return &scheme{ 48 | rng: rng, 49 | } 50 | } 51 | 52 | func (e *scheme) GeneratePrivateKey(rng io.Reader) nike.PrivateKey { 53 | privKey, err := NewKeypair(rng) 54 | if err != nil { 55 | panic(err) 56 | } 57 | return privKey 58 | } 59 | 60 | func (e *scheme) GenerateKeyPairFromEntropy(rng io.Reader) (nike.PublicKey, nike.PrivateKey, error) { 61 | privKey, err := NewKeypair(rng) 62 | if err != nil { 63 | return nil, nil, err 64 | } 65 | return privKey.Public(), privKey, nil 66 | } 67 | 68 | func (e *scheme) GenerateKeyPair() (nike.PublicKey, nike.PrivateKey, error) { 69 | return e.GenerateKeyPairFromEntropy(e.rng) 70 | } 71 | 72 | func (e *scheme) Name() string { 73 | return "x448" 74 | } 75 | 76 | // PublicKeySize returns the size in bytes of the public key. 77 | func (e *scheme) PublicKeySize() int { 78 | return PublicKeySize 79 | } 80 | 81 | // PrivateKeySize returns the size in bytes of the private key. 82 | func (e *scheme) PrivateKeySize() int { 83 | return PublicKeySize 84 | } 85 | 86 | // NewEmptyPublicKey returns an uninitialized 87 | // PublicKey which is suitable to be loaded 88 | // via some serialization format via FromBytes 89 | // or FromPEMFile methods. 90 | func (e *scheme) NewEmptyPublicKey() nike.PublicKey { 91 | return &PublicKey{ 92 | pubBytes: new(x448.Key), 93 | } 94 | } 95 | 96 | // NewEmptyPrivateKey returns an uninitialized 97 | // PrivateKey which is suitable to be loaded 98 | // via some serialization format via FromBytes 99 | // or FromPEMFile methods. 100 | func (e *scheme) NewEmptyPrivateKey() nike.PrivateKey { 101 | return &PrivateKey{ 102 | privBytes: new(x448.Key), 103 | } 104 | } 105 | 106 | // DeriveSecret derives a shared secret given a private key 107 | // from one party and a public key from another. 108 | func (e *scheme) DeriveSecret(privKey nike.PrivateKey, pubKey nike.PublicKey) []byte { 109 | sharedSecret := Exp(privKey.(*PrivateKey).privBytes, (pubKey.(*PublicKey)).pubBytes) 110 | return sharedSecret[:] 111 | } 112 | 113 | // DerivePublicKey derives a public key given a private key. 114 | func (e *scheme) DerivePublicKey(privKey nike.PrivateKey) nike.PublicKey { 115 | return privKey.(*PrivateKey).Public() 116 | } 117 | 118 | func (e *scheme) Blind(groupMember nike.PublicKey, blindingFactor nike.PrivateKey) nike.PublicKey { 119 | sharedSecret := Exp(blindingFactor.(*PrivateKey).privBytes, groupMember.(*PublicKey).pubBytes) 120 | pubKey := new(PublicKey) 121 | err := pubKey.FromBytes(sharedSecret) 122 | if err != nil { 123 | panic(err) 124 | } 125 | 126 | return pubKey 127 | } 128 | 129 | // UnmarshalBinaryPublicKey loads a public key from byte slice. 130 | func (e *scheme) UnmarshalBinaryPublicKey(b []byte) (nike.PublicKey, error) { 131 | pubKey := new(PublicKey) 132 | err := pubKey.FromBytes(b) 133 | if err != nil { 134 | return nil, err 135 | } 136 | return pubKey, nil 137 | } 138 | 139 | // UnmarshalBinaryPrivateKey loads a private key from byte slice. 140 | func (e *scheme) UnmarshalBinaryPrivateKey(b []byte) (nike.PrivateKey, error) { 141 | privKey := new(PrivateKey) 142 | err := privKey.FromBytes(b) 143 | if err != nil { 144 | return nil, err 145 | } 146 | return privKey, err 147 | } 148 | 149 | type PrivateKey struct { 150 | privBytes *x448.Key 151 | } 152 | 153 | func NewKeypair(rng io.Reader) (nike.PrivateKey, error) { 154 | privkey := new(x448.Key) 155 | count, err := rng.Read(privkey[:]) 156 | if err != nil { 157 | return nil, err 158 | } 159 | if count != GroupElementLength { 160 | return nil, errors.New("read wrong number of bytes from rng") 161 | } 162 | return &PrivateKey{ 163 | privBytes: privkey, 164 | }, nil 165 | } 166 | 167 | func (p *PrivateKey) Public() nike.PublicKey { 168 | pubKey := Scheme(rand.Reader).NewEmptyPublicKey() 169 | expG(pubKey.(*PublicKey).pubBytes, p.privBytes) 170 | return pubKey 171 | } 172 | 173 | func (p *PrivateKey) Reset() { 174 | zeros := make([]byte, PrivateKeySize) 175 | err := p.FromBytes(zeros) 176 | if err != nil { 177 | panic(err) 178 | } 179 | } 180 | 181 | func (p *PrivateKey) Bytes() []byte { 182 | b := make([]byte, PublicKeySize) 183 | copy(b, p.privBytes[:]) 184 | return b 185 | } 186 | 187 | func (p *PrivateKey) FromBytes(data []byte) error { 188 | if len(data) != PrivateKeySize { 189 | return errInvalidKey 190 | } 191 | 192 | p.privBytes = new(x448.Key) 193 | copy(p.privBytes[:], data) 194 | 195 | return nil 196 | } 197 | 198 | func (p *PrivateKey) MarshalBinary() ([]byte, error) { 199 | return p.Bytes(), nil 200 | } 201 | 202 | func (p *PrivateKey) MarshalText() ([]byte, error) { 203 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 204 | } 205 | 206 | func (p *PrivateKey) UnmarshalBinary(data []byte) error { 207 | return p.FromBytes(data) 208 | } 209 | 210 | func (p *PrivateKey) UnmarshalText(data []byte) error { 211 | raw, err := base64.StdEncoding.DecodeString(string(data)) 212 | if err != nil { 213 | return err 214 | } 215 | return p.FromBytes(raw) 216 | } 217 | 218 | type PublicKey struct { 219 | pubBytes *x448.Key 220 | } 221 | 222 | func (p *PublicKey) Blind(blindingFactor nike.PrivateKey) error { 223 | _, ok := blindingFactor.(*PrivateKey) 224 | if !ok { 225 | return errors.New("blindingFactor nike.PrivateKey must be the same concrete type as x448.PublicKey") 226 | } 227 | pubBytes := Exp(blindingFactor.(*PrivateKey).privBytes, p.pubBytes) 228 | copy(p.pubBytes[:], pubBytes) 229 | util.ExplicitBzero(pubBytes) 230 | return nil 231 | } 232 | 233 | func (p *PublicKey) Reset() { 234 | zeros := make([]byte, PublicKeySize) 235 | err := p.FromBytes(zeros) 236 | if err != nil { 237 | panic(err) 238 | } 239 | } 240 | 241 | func (p *PublicKey) Bytes() []byte { 242 | b := make([]byte, PublicKeySize) 243 | copy(b, p.pubBytes[:]) 244 | return b 245 | } 246 | 247 | func (p *PublicKey) FromBytes(data []byte) error { 248 | if len(data) != PublicKeySize { 249 | return errInvalidKey 250 | } 251 | 252 | p.pubBytes = new(x448.Key) 253 | copy(p.pubBytes[:], data) 254 | 255 | return nil 256 | } 257 | 258 | func (p *PublicKey) MarshalBinary() ([]byte, error) { 259 | return p.Bytes(), nil 260 | } 261 | 262 | func (p *PublicKey) MarshalText() ([]byte, error) { 263 | return []byte(base64.StdEncoding.EncodeToString(p.Bytes())), nil 264 | } 265 | 266 | func (p *PublicKey) UnmarshalBinary(data []byte) error { 267 | return p.FromBytes(data) 268 | } 269 | 270 | func (p *PublicKey) UnmarshalText(data []byte) error { 271 | raw, err := base64.StdEncoding.DecodeString(string(data)) 272 | if err != nil { 273 | return err 274 | } 275 | return p.FromBytes(raw) 276 | } 277 | 278 | // Exp returns the group element, the result of x^y, over the ECDH group. 279 | func Exp(x, y *x448.Key) []byte { 280 | sharedSecret := new(x448.Key) 281 | ok := x448.Shared(sharedSecret, x, y) 282 | if !ok { 283 | panic("x448.Shared failed") 284 | } 285 | return sharedSecret[:] 286 | } 287 | 288 | func expG(dst, y *x448.Key) { 289 | x448.KeyGen(dst, y) 290 | } 291 | --------------------------------------------------------------------------------