Add ToECDSA method to PrivateKey

This commit is contained in:
Zach Kelling
2025-08-19 09:04:15 +00:00
parent fb67a4df02
commit 8035e03f3c
30 changed files with 1757 additions and 3869 deletions
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Darya Kaviani
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-215
View File
@@ -1,215 +0,0 @@
# Corona Production Deployment Guide
## Overview
Corona is a lattice-based threshold signature scheme implementation designed for integration with the Lux Network consensus protocol. This guide covers production deployment considerations and improvements made for production readiness.
## Version: v0.1.0
### Key Features
- Two-round threshold signature protocol
- Post-quantum secure (based on LWE)
- Optimized for consensus integration
- Configurable parameters
- Production-ready error handling
## Production Improvements
### 1. Module Structure
- Updated module name to `github.com/luxfi/corona`
- Proper package organization for production use
### 2. Configuration Management
- External configuration support via JSON files
- Environment variable overrides
- Default configurations for different network types
### 3. Error Handling
- Comprehensive error wrapping with context
- Panic recovery mechanisms
- Structured error types for better debugging
### 4. Network Resilience
- Connection retry logic
- Timeout configurations
- Concurrent-safe socket management
### 5. Consensus Integration
- Configurable consensus timeouts
- Node ID management
- Signature concurrency controls
## Deployment
### Prerequisites
- Go 1.19 or higher
- Network connectivity between signature parties
- Sufficient system resources (see below)
### Resource Requirements
#### Minimum (5-party network)
- CPU: 2 cores
- RAM: 4GB
- Network: 100 Mbps
- Storage: 1GB
#### Recommended (21-party mainnet)
- CPU: 8 cores
- RAM: 16GB
- Network: 1 Gbps
- Storage: 10GB
### Configuration
Create a `config.json` file:
```json
{
"network": {
"listen_port": 8080,
"peer_addresses": [
"node1.lux.network:8080",
"node2.lux.network:8080"
],
"timeout_seconds": 30,
"max_retries": 3
},
"signature": {
"party_count": 21,
"threshold": 15,
"m": 8,
"n": 7,
"key_size": 32
},
"consensus": {
"enabled": true,
"consensus_timeout_ms": 9630,
"signature_timeout_ms": 2000,
"node_id": "NodeID-xxx"
}
}
```
### Running
#### Single Node
```bash
./corona <party_id> <iterations> <party_count> --config config.json
```
#### Docker Deployment
```dockerfile
FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o corona .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/corona .
COPY config.json .
CMD ["./corona"]
```
### Monitoring
Key metrics to monitor:
- Signature generation time (target: < 2s)
- Network latency between parties
- Memory usage
- CPU utilization
- Failed signature attempts
### Security Considerations
1. **Key Management**
- Store keys in secure hardware (HSM)
- Rotate keys periodically
- Use separate keys for test/mainnet
2. **Network Security**
- Use TLS for all communications
- Implement IP whitelisting
- Monitor for anomalous behavior
3. **Access Control**
- Limit access to signature nodes
- Use strong authentication
- Audit all access attempts
## Integration with Lux Consensus
### Interface
```go
type ThresholdSigner interface {
// Initialize the signer with configuration
Init(config *Config) error
// Generate a threshold signature
Sign(message []byte, signers []int) (*Signature, error)
// Verify a threshold signature
Verify(signature *Signature, message []byte) bool
// Get signer status
Status() (*SignerStatus, error)
}
```
### Consensus Parameters
- Mainnet: 21 nodes, 15 threshold, 9.63s timeout
- Testnet: 11 nodes, 7 threshold, 6.3s timeout
- Local: 5 nodes, 3 threshold, 3.69s timeout
## Troubleshooting
### Common Issues
1. **Connection Failures**
- Check firewall rules
- Verify peer addresses
- Ensure network connectivity
2. **Signature Timeouts**
- Increase timeout values
- Check network latency
- Verify all parties are online
3. **Performance Issues**
- Enable performance profiling
- Check system resources
- Optimize network topology
### Debug Mode
```bash
CORONA_DEBUG=true ./corona <args>
```
## Future Improvements
1. **Performance Optimizations**
- Implement signature aggregation
- Optimize polynomial operations
- Add GPU acceleration support
2. **Operational Features**
- Prometheus metrics export
- Health check endpoints
- Automatic key rotation
3. **Consensus Enhancements**
- Direct consensus integration
- Signature caching
- Batch signature support
## Support
For issues and questions:
- GitHub Issues: https://github.com/luxfi/corona/issues
- Security: security@lux.network
## License
Apache 2.0 - See LICENSE file
-26
View File
@@ -1,26 +0,0 @@
# Corona
This is a pure Golang implementation of Corona [eprint.iacr.org/2024/1113](https://eprint.iacr.org/2024/1113), a practical two-round threshold signature scheme from LWE, optimized for integration with the Lux Network consensus protocol.
**Version:** v0.1.0
**Status:** This implementation has been enhanced for production use with the Lux Network, including improved error handling, configuration management, and consensus integration support. However, it should still undergo thorough security auditing before mainnet deployment.
### Codebase Overview
- `networking/`
- `networking.go`: Includes the networking stack which allows signers to form peer-to-peer network connections with other parties. Each party concurrently communicates with every other party by serializing and sending its messages through outgoing TCP sockets, while simultaneously receiving and processing incoming messages.
- `primitives/`
- `hash.go`: Hashes, MACs, PRFs involved in the scheme.
- `shamir.go`: Shamir secret-sharing for secret key vector.
- `sign/`
- `config.go`: Parameters for concrete instantiation.
- `local.go`: Locally runs the scheme on a single machine for a given number of parties.
- `sign.go`: Core functionality of the scheme.
- `utils/`
- `utils.go`: Helpers related to NTT and Montgomery conversions, multiplying, and initializing matrices and vectors of ring elements.
- `utils-naive.go`: This is note used in the current version, but can be used for testing. It implements convolution-based naive ring-element multiplication.
- `main.go`: Run the code with `go run main.go id iters parties` where `id` is the party ID of the signer running the code (use `l` if you want to run the scheme locally), `iters` is the number of iterations to average the latencies over if you are benchmarking (if not, just use 1), and `parties` is the total number of parties. This is currently a full-threshold implementation. For testing a smaller threshold, set the `Threshold` config parameter with a different value, and use `ShamirSecretSharingGeneral`.
### License
Corona is licensed under the Apache 2.0 License. See [LICENSE](https://github.com/daryakaviani/corona/blob/main/LICENSE).
-135
View File
@@ -1,135 +0,0 @@
package config
import (
"encoding/json"
"os"
)
// Config holds all configurable parameters for the signature scheme
type Config struct {
// Network parameters
NetworkConfig NetworkConfig `json:"network"`
// Signature scheme parameters
SignatureParams SignatureParams `json:"signature"`
// Consensus integration
ConsensusConfig ConsensusConfig `json:"consensus"`
}
// NetworkConfig holds network-related configuration
type NetworkConfig struct {
ListenPort int `json:"listen_port"`
PeerAddresses []string `json:"peer_addresses"`
Timeout int `json:"timeout_seconds"`
MaxRetries int `json:"max_retries"`
}
// SignatureParams holds signature scheme parameters
type SignatureParams struct {
M int `json:"m"`
N int `json:"n"`
Dbar int `json:"dbar"`
B float64 `json:"b"`
Kappa int `json:"kappa"`
LogN int `json:"log_n"`
SigmaE float64 `json:"sigma_e"`
SigmaStar float64 `json:"sigma_star"`
SigmaU float64 `json:"sigma_u"`
KeySize int `json:"key_size"`
Q uint64 `json:"q"`
QNu uint64 `json:"q_nu"`
QXi uint64 `json:"q_xi"`
TrustedDealerID int `json:"trusted_dealer_id"`
CombinerID int `json:"combiner_id"`
Xi int `json:"xi"`
Nu int `json:"nu"`
EtaEpsilon float64 `json:"eta_epsilon"`
PartyCount int `json:"party_count"`
Threshold int `json:"threshold"`
}
// ConsensusConfig holds Lux consensus integration parameters
type ConsensusConfig struct {
Enabled bool `json:"enabled"`
ConsensusTimeout int `json:"consensus_timeout_ms"`
SignatureTimeout int `json:"signature_timeout_ms"`
MaxConcurrentSigs int `json:"max_concurrent_signatures"`
NodeID string `json:"node_id"`
}
// DefaultConfig returns the default configuration
func DefaultConfig() *Config {
return &Config{
NetworkConfig: NetworkConfig{
ListenPort: 8080,
PeerAddresses: []string{},
Timeout: 30,
MaxRetries: 3,
},
SignatureParams: SignatureParams{
M: 8,
N: 7,
Dbar: 48,
B: 430070539612332.205811372782969,
Kappa: 23,
LogN: 8,
SigmaE: 6.108187070284607,
SigmaStar: 172852667880.2713189548230532887787,
SigmaU: 163961331.5239387,
KeySize: 32,
Q: 0x1000000004A01,
QNu: 0x80000,
QXi: 0x40000,
TrustedDealerID: 0,
CombinerID: 1,
Xi: 30,
Nu: 29,
EtaEpsilon: 2.650104,
PartyCount: 3,
Threshold: 3,
},
ConsensusConfig: ConsensusConfig{
Enabled: false,
ConsensusTimeout: 5000,
SignatureTimeout: 2000,
MaxConcurrentSigs: 10,
NodeID: "",
},
}
}
// LoadConfig loads configuration from a JSON file
func LoadConfig(path string) (*Config, error) {
config := DefaultConfig()
if path == "" {
return config, nil
}
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
decoder := json.NewDecoder(file)
if err := decoder.Decode(config); err != nil {
return nil, err
}
return config, nil
}
// SaveConfig saves configuration to a JSON file
func (c *Config) SaveConfig(path string) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(c)
}
-237
View File
@@ -1,237 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package corona implements post-quantum ring signatures
// Based on lattice cryptography for privacy-preserving quantum-resistant signatures
package corona
import (
"crypto/rand"
"errors"
"io"
"github.com/cloudflare/circl/sign/dilithium/dilithium3"
)
// Corona provides post-quantum ring signatures
// Ring signatures allow a signer to prove membership in a group without revealing identity
// This implementation uses lattice-based cryptography for quantum resistance
// RingSize defines the size of the anonymity set
type RingSize int
const (
SmallRing RingSize = 8 // 8 members
MediumRing RingSize = 16 // 16 members
LargeRing RingSize = 32 // 32 members
XLargeRing RingSize = 64 // 64 members
)
// PrivateKey represents a Corona private key
type PrivateKey struct {
index int // Index in the ring
key dilithium3.PrivateKey // Underlying lattice key
ringSize RingSize
}
// PublicKey represents a single public key in the ring
type PublicKey struct {
key dilithium3.PublicKey
}
// Ring represents a set of public keys forming an anonymity set
type Ring struct {
size RingSize
keys []*PublicKey
}
// RingSignature represents a ring signature
type RingSignature struct {
ringSize RingSize
signature []byte
challenge []byte
responses [][]byte
}
// GenerateKey generates a new Corona keypair
func GenerateKey(rand io.Reader) (*PrivateKey, *PublicKey, error) {
if rand == nil {
rand = rand.Reader
}
// Generate underlying Dilithium key
pubKey, privKey, err := dilithium3.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
return &PrivateKey{
index: -1, // Not part of a ring yet
key: privKey,
ringSize: SmallRing,
}, &PublicKey{
key: pubKey,
}, nil
}
// CreateRing creates a ring from a set of public keys
func CreateRing(keys []*PublicKey, ringSize RingSize) (*Ring, error) {
if len(keys) != int(ringSize) {
return nil, errors.New("invalid number of keys for ring size")
}
return &Ring{
size: ringSize,
keys: keys,
}, nil
}
// Sign creates a ring signature for the given message
func (priv *PrivateKey) SignRing(message []byte, ring *Ring, index int) (*RingSignature, error) {
if index < 0 || index >= len(ring.keys) {
return nil, errors.New("invalid index in ring")
}
// Simplified ring signature (in production, use proper ring signature scheme)
// This is a placeholder - real implementation would use a lattice-based ring signature
// For now, create a standard signature and obfuscate the signer
sig := dilithium3.Sign(priv.key, message)
// Create fake responses for other ring members
responses := make([][]byte, ring.size)
challenge := make([]byte, 32)
rand.Read(challenge)
for i := 0; i < int(ring.size); i++ {
if i == index {
responses[i] = sig[:256] // Part of real signature
} else {
// Generate fake response
responses[i] = make([]byte, 256)
rand.Read(responses[i])
}
}
return &RingSignature{
ringSize: ring.size,
signature: sig,
challenge: challenge,
responses: responses,
}, nil
}
// Verify checks if a ring signature is valid
func (ring *Ring) Verify(message []byte, sig *RingSignature) bool {
if sig.ringSize != ring.size {
return false
}
// Simplified verification (placeholder)
// Real implementation would verify the ring signature properties
// For now, try to verify against each public key
for _, pubKey := range ring.keys {
if dilithium3.Verify(pubKey.key, message, sig.signature) {
return true
}
}
return false
}
// Bytes serializes a ring signature
func (sig *RingSignature) Bytes() []byte {
size := 1 + len(sig.signature) + len(sig.challenge)
for _, resp := range sig.responses {
size += len(resp)
}
result := make([]byte, 0, size)
result = append(result, byte(sig.ringSize))
result = append(result, sig.signature...)
result = append(result, sig.challenge...)
for _, resp := range sig.responses {
result = append(result, resp...)
}
return result
}
// RingSignatureFromBytes deserializes a ring signature
func RingSignatureFromBytes(data []byte) (*RingSignature, error) {
if len(data) < 1 {
return nil, errors.New("invalid ring signature data")
}
ringSize := RingSize(data[0])
// Parse signature components (simplified)
// Real implementation would properly parse all components
return &RingSignature{
ringSize: ringSize,
signature: data[1:],
challenge: make([]byte, 32),
responses: make([][]byte, ringSize),
}, nil
}
// GetSignatureSize returns the size of a ring signature
func GetSignatureSize(ringSize RingSize) int {
// Approximate size: ring_size * response_size + signature + challenge
return int(ringSize)*256 + 2420 + 32
}
// LinkableRingSignature provides linkable ring signatures
// These allow detection of double-signing while maintaining anonymity
type LinkableRingSignature struct {
RingSignature
linkingTag []byte // Tag that's the same for all signatures by the same key
}
// SignLinkableRing creates a linkable ring signature
func (priv *PrivateKey) SignLinkableRing(message []byte, ring *Ring, index int) (*LinkableRingSignature, error) {
basicSig, err := priv.SignRing(message, ring, index)
if err != nil {
return nil, err
}
// Generate linking tag (deterministic based on private key)
// This allows detection of double-signing
linkingTag := make([]byte, 32)
// In production, derive this deterministically from private key
copy(linkingTag, priv.key.Bytes()[:32])
return &LinkableRingSignature{
RingSignature: *basicSig,
linkingTag: linkingTag,
}, nil
}
// VerifyLinkable verifies a linkable ring signature
func (ring *Ring) VerifyLinkable(message []byte, sig *LinkableRingSignature) bool {
// First verify the basic ring signature
if !ring.Verify(message, &sig.RingSignature) {
return false
}
// Additional verification for linkability
// Check that linking tag is properly formed
return len(sig.linkingTag) == 32
}
// IsLinked checks if two linkable signatures were created by the same signer
func IsLinked(sig1, sig2 *LinkableRingSignature) bool {
if len(sig1.linkingTag) != len(sig2.linkingTag) {
return false
}
for i := range sig1.linkingTag {
if sig1.linkingTag[i] != sig2.linkingTag[i] {
return false
}
}
return true
}
-26
View File
@@ -1,26 +0,0 @@
module github.com/luxfi/corona
go 1.21
toolchain go1.24.5
require (
github.com/montanaflynn/stats v0.7.1
github.com/tuneinsight/lattigo/v5 v5.0.2
github.com/zeebo/blake3 v0.2.3
)
require (
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/sys v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-42
View File
@@ -1,42 +0,0 @@
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tuneinsight/lattigo/v5 v5.0.2 h1:g+WmQK0G04nldAPnthgBFsfMtCJRLcfwME+cR9hLuIg=
github.com/tuneinsight/lattigo/v5 v5.0.2/go.mod h1:mmynIHGOeVRGYzuHRdZNbDLJWe8WHDYCgYkl9WZrHN0=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-420
View File
@@ -1,420 +0,0 @@
package networking
import (
"bufio"
"encoding/binary"
"fmt"
"log"
"net"
"sync"
"time"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/structs"
)
type Communicator interface {
Send(dst int, msg []byte) (int, error)
Recv(src int) ([]byte, int, error)
Close() error
}
type P2PComm struct {
Socks map[int]*net.Conn
Rank int
mu sync.Mutex // Added mutex for safe concurrent access
}
func (comm *P2PComm) SetSock(key int, conn *net.Conn) {
comm.mu.Lock()
defer comm.mu.Unlock()
comm.Socks[key] = conn
}
func (comm *P2PComm) GetSock(key int) *net.Conn {
comm.mu.Lock()
defer comm.mu.Unlock()
return comm.Socks[key]
}
func (comm *P2PComm) SendBytes(writer *bufio.Writer, dst int, msg []byte) (int, error) {
length := make([]byte, 4)
binary.BigEndian.PutUint32(length, uint32(len(msg)))
n, err := writer.Write(length)
if err != nil {
return 0, err
}
totalBytesSent := n
for len(msg) > 0 {
n, err = writer.Write(msg)
if err != nil {
return 0, err
}
totalBytesSent += n
msg = msg[n:]
}
err = writer.Flush()
if err != nil {
return 0, err
}
return totalBytesSent, nil
}
func (comm *P2PComm) Recv(reader *bufio.Reader, src int) ([]byte, int, error) {
lengthBuf := make([]byte, 4)
totalBytesRead := 0
for totalBytesRead < 4 {
n, err := reader.Read(lengthBuf[totalBytesRead:])
if err != nil {
return nil, totalBytesRead, err
}
totalBytesRead += n
}
length := binary.BigEndian.Uint32(lengthBuf)
data := make([]byte, length)
bytesRead := 0
for bytesRead < int(length) {
n, err := reader.Read(data[bytesRead:])
if err != nil {
return nil, totalBytesRead, err
}
bytesRead += n
totalBytesRead += n
}
return data, totalBytesRead, nil
}
func (comm *P2PComm) Close() error {
comm.mu.Lock()
defer comm.mu.Unlock()
for _, sock := range comm.Socks {
err := (*sock).Close()
if err != nil {
return err
}
}
return nil
}
func (comm *P2PComm) SendVector(writer *bufio.Writer, dst int, msg structs.Vector[ring.Poly]) {
if _, err := msg.WriteTo(writer); err != nil {
log.Fatalf("Failed to write vector: %v", err)
}
if err := writer.Flush(); err != nil {
log.Fatalf("Failed to flush writer: %v", err)
}
}
func (comm *P2PComm) RecvVector(reader *bufio.Reader, src int, length int) structs.Vector[ring.Poly] {
vec := make(structs.Vector[ring.Poly], length)
if _, err := vec.ReadFrom(reader); err != nil {
log.Fatalf("Failed to read vector: %v", err)
}
return vec
}
func (comm *P2PComm) SendMatrix(writer *bufio.Writer, dst int, msg structs.Matrix[ring.Poly]) {
if _, err := msg.WriteTo(writer); err != nil {
log.Fatalf("Error sending matrix: %v", err)
}
if err := writer.Flush(); err != nil {
log.Fatalf("Failed to flush writer: %v", err)
}
}
func (comm *P2PComm) RecvMatrix(reader *bufio.Reader, src int, length int) structs.Matrix[ring.Poly] {
matrix := make(structs.Matrix[ring.Poly], length)
if _, err := matrix.ReadFrom(reader); err != nil {
log.Fatalf("Failed to read matrix: %v", err)
}
return matrix
}
func (comm *P2PComm) SendBytesSlice(writer *bufio.Writer, dst int, data [][]byte) {
numSlices := uint32(len(data))
if err := binary.Write(writer, binary.BigEndian, numSlices); err != nil {
log.Fatalf("Failed to write number of slices: %v", err)
}
for _, slice := range data {
length := uint32(len(slice))
if err := binary.Write(writer, binary.BigEndian, length); err != nil {
log.Fatalf("Failed to write slice length: %v", err)
}
for len(slice) > 0 {
n, err := writer.Write(slice)
if err != nil {
log.Fatalf("Failed to write slice data: %v", err)
}
slice = slice[n:]
}
}
if err := writer.Flush(); err != nil {
log.Fatalf("Failed to flush writer: %v", err)
}
}
func (comm *P2PComm) RecvBytesSlice(reader *bufio.Reader, src int) [][]byte {
var numSlices uint32
if err := binary.Read(reader, binary.BigEndian, &numSlices); err != nil {
log.Fatalf("Failed to read number of slices: %v", err)
}
data := make([][]byte, numSlices)
for i := uint32(0); i < numSlices; i++ {
var length uint32
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
log.Fatalf("Failed to read slice length: %v", err)
}
slice := make([]byte, length)
bytesRead := 0
for bytesRead < int(length) {
n, err := reader.Read(slice[bytesRead:])
if err != nil {
log.Fatalf("Failed to read slice data: %v", err)
}
bytesRead += n
}
data[i] = slice
}
return data
}
func (comm *P2PComm) SendBytesMap(writer *bufio.Writer, dst int, data map[int][]byte) {
numEntries := uint32(len(data))
if err := binary.Write(writer, binary.BigEndian, numEntries); err != nil {
log.Fatalf("Failed to write number of map entries: %v", err)
}
for key, value := range data {
if err := binary.Write(writer, binary.BigEndian, int32(key)); err != nil {
log.Fatalf("Failed to write map key: %v", err)
}
length := uint32(len(value))
if err := binary.Write(writer, binary.BigEndian, length); err != nil {
log.Fatalf("Failed to write value length: %v", err)
}
for len(value) > 0 {
n, err := writer.Write(value)
if err != nil {
log.Fatalf("Failed to write value data: %v", err)
}
value = value[n:]
}
}
if err := writer.Flush(); err != nil {
log.Fatalf("Failed to flush writer: %v", err)
}
}
func (comm *P2PComm) RecvBytesMap(reader *bufio.Reader, src int) map[int][]byte {
var numEntries uint32
if err := binary.Read(reader, binary.BigEndian, &numEntries); err != nil {
log.Fatalf("Failed to read number of map entries: %v", err)
}
data := make(map[int][]byte, numEntries)
for i := uint32(0); i < numEntries; i++ {
var key int32
if err := binary.Read(reader, binary.BigEndian, &key); err != nil {
log.Fatalf("Failed to read map key: %v", err)
}
var length uint32
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
log.Fatalf("Failed to read value length: %v", err)
}
value := make([]byte, length)
bytesRead := 0
for bytesRead < int(length) {
n, err := reader.Read(value[bytesRead:])
if err != nil {
log.Fatalf("Failed to read value data: %v", err)
}
bytesRead += n
}
data[int(key)] = value
}
return data
}
func (comm *P2PComm) SendBytesSliceMap(writer *bufio.Writer, dst int, data map[int][][]byte) {
numEntries := uint32(len(data))
if err := binary.Write(writer, binary.BigEndian, numEntries); err != nil {
log.Fatalf("Failed to write number of map entries: %v", err)
}
for key, value := range data {
if err := binary.Write(writer, binary.BigEndian, int32(key)); err != nil {
log.Fatalf("Failed to write map key: %v", err)
}
numSlices := uint32(len(value))
if err := binary.Write(writer, binary.BigEndian, numSlices); err != nil {
log.Fatalf("Failed to write number of slices: %v", err)
}
for _, slice := range value {
length := uint32(len(slice))
if err := binary.Write(writer, binary.BigEndian, length); err != nil {
log.Fatalf("Failed to write slice length: %v", err)
}
for len(slice) > 0 {
n, err := writer.Write(slice)
if err != nil {
log.Fatalf("Failed to write slice data: %v", err)
}
slice = slice[n:]
}
}
}
if err := writer.Flush(); err != nil {
log.Fatalf("Failed to flush writer: %v", err)
}
}
func (comm *P2PComm) RecvBytesSliceMap(reader *bufio.Reader, src int) map[int][][]byte {
var numEntries uint32
if err := binary.Read(reader, binary.BigEndian, &numEntries); err != nil {
log.Fatalf("Failed to read number of map entries: %v", err)
}
data := make(map[int][][]byte, numEntries)
for i := uint32(0); i < numEntries; i++ {
var key int32
if err := binary.Read(reader, binary.BigEndian, &key); err != nil {
log.Fatalf("Failed to read map key: %v", err)
}
var numSlices uint32
if err := binary.Read(reader, binary.BigEndian, &numSlices); err != nil {
log.Fatalf("Failed to read number of slices: %v", err)
}
slices := make([][]byte, numSlices)
for j := uint32(0); j < numSlices; j++ {
var length uint32
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
log.Fatalf("Failed to read slice length: %v", err)
}
slice := make([]byte, length)
bytesRead := 0
for bytesRead < int(length) {
n, err := reader.Read(slice[bytesRead:])
if err != nil {
log.Fatalf("Failed to read slice data: %v", err)
}
bytesRead += n
}
slices[j] = slice
}
data[int(key)] = slices
}
return data
}
func ListenTCP(comm *P2PComm, port string, src int) {
l, err := net.Listen("tcp", "0.0.0.0:"+port)
if err != nil {
log.Fatal(err)
return
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Println(err)
continue
}
comm.SetSock(src, &conn)
break
}
}
func DialTCP(comm *P2PComm, dst int, address string) {
log.Println("Trying to dial", dst, "at address", address)
// Check if the socket for the destination is already initialized
sock := comm.GetSock(dst)
if sock != nil && *sock != nil {
log.Printf("Socket for destination %d is already initialized.", dst)
return
}
var conn net.Conn
var err error
for i := 0; i < 10; i++ {
conn, err = net.Dial("tcp", address)
if err == nil {
break
}
log.Printf("Failed to dial TCP to %s: %v, retrying...", address, err)
time.Sleep(1 * time.Second)
}
if err != nil {
log.Fatalf("Failed to dial TCP to %s after retries: %v", address, err)
return
}
comm.SetSock(dst, &conn)
log.Println("Successfully connected to", address)
}
func EstablishConnections(wg *sync.WaitGroup, comm *P2PComm, partyID int, totalParties int) {
defer wg.Done()
var localWg sync.WaitGroup
// List of IP addresses of all parties
ips := []string{"50.18.79.144", "34.226.247.5", "18.199.237.28", "34.244.57.20", "54.248.28.234", "47.129.58.89", "54.232.160.71", "3.107.98.51"}
for otherID := 0; otherID < totalParties; otherID++ {
if partyID != otherID {
localWg.Add(1)
go func(otherID int) {
defer localWg.Done()
port := 6000 + calculatePortOffset(partyID, otherID)
address := fmt.Sprintf("%s:%d", ips[otherID], port)
if partyID < otherID {
ListenTCP(comm, fmt.Sprintf("%d", port), otherID)
} else {
DialTCP(comm, otherID, address)
}
}(otherID)
}
}
localWg.Wait()
}
func calculatePortOffset(partyID, otherID int) int {
if partyID < otherID {
return partyID*100 + otherID
}
return otherID*100 + partyID
}
-151
View File
@@ -1,151 +0,0 @@
package primitives
import (
"bytes"
"encoding/binary"
"github.com/luxfi/corona/utils"
"log"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/sampling"
"github.com/tuneinsight/lattigo/v5/utils/structs"
"github.com/zeebo/blake3"
)
const keySize = 32
// PRNGKey generates a key for PRNG using the secret key share
func PRNGKey(skShare structs.Vector[ring.Poly]) []byte {
hasher := blake3.New()
buf := new(bytes.Buffer)
skShare.WriteTo(buf)
hasher.Write(buf.Bytes())
skHash := hasher.Sum(nil)
return skHash[:keySize]
}
// GenerateMAC generates a MAC for a given TildeD matrix and mask
func GenerateMAC(TildeD structs.Matrix[ring.Poly], MACKey []byte, partyID int, sid int, T []int, otherParty int, verify bool) []byte {
hasher := blake3.New()
buf := new(bytes.Buffer)
if verify {
binary.Write(buf, binary.BigEndian, int64(otherParty))
} else {
binary.Write(buf, binary.BigEndian, int64(partyID))
}
binary.Write(buf, binary.BigEndian, MACKey)
TildeD.WriteTo(buf)
binary.Write(buf, binary.BigEndian, int64(sid))
binary.Write(buf, binary.BigEndian, T)
hasher.Write(buf.Bytes())
MAC := hasher.Sum(nil)
return MAC[:keySize]
}
// Hashes parameters to a Gaussian distribution
func GaussianHash(r *ring.Ring, hash []byte, mu string, sigmaU float64, boundU float64, length int) structs.Vector[ring.Poly] {
hasher := blake3.New()
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, hash)
buf.WriteString(mu)
hasher.Write(buf.Bytes())
hashOutput := hasher.Sum(nil)
prng, _ := sampling.NewKeyedPRNG(hashOutput[:keySize])
gaussianParams := ring.DiscreteGaussian{Sigma: sigmaU, Bound: boundU}
hashGaussianSampler := ring.NewGaussianSampler(prng, r, gaussianParams, false)
return utils.SamplePolyVector(r, length, hashGaussianSampler, true, true)
}
// PRF generates pseudorandom ring elements
func PRF(r *ring.Ring, sd_ij []byte, PRFKey []byte, mu string, hash []byte, n int) structs.Vector[ring.Poly] {
hasher := blake3.New()
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, PRFKey)
binary.Write(buf, binary.BigEndian, sd_ij)
binary.Write(buf, binary.BigEndian, hash)
buf.WriteString(mu)
hasher.Write(buf.Bytes())
hashOutput := hasher.Sum(nil)
prng, _ := sampling.NewKeyedPRNG(hashOutput[:keySize])
PRFUniformSampler := ring.NewUniformSampler(prng, r)
mask := utils.SamplePolyVector(r, n, PRFUniformSampler, true, true)
return mask
}
// Hashes precomputable values
func Hash(A structs.Matrix[ring.Poly], b structs.Vector[ring.Poly], D map[int]structs.Matrix[ring.Poly], sid int, T []int) []byte {
hasher := blake3.New()
buf := new(bytes.Buffer)
if _, err := A.WriteTo(buf); err != nil {
log.Fatalf("Error writing matrix A: %v\n", err)
}
if _, err := b.WriteTo(buf); err != nil {
log.Fatalf("Error writing vector b: %v\n", err)
}
binary.Write(buf, binary.BigEndian, int64(sid))
binary.Write(buf, binary.BigEndian, T)
for i := 0; i < len(D); i++ {
if _, err := D[i].WriteTo(buf); err != nil {
log.Fatalf("Error writing matrix D_i: %v\n", err)
}
}
hasher.Write(buf.Bytes())
hashOutput := hasher.Sum(nil)
return hashOutput[:keySize]
}
// Hashes to low norm ring elements
func LowNormHash(r *ring.Ring, A structs.Matrix[ring.Poly], b structs.Vector[ring.Poly], h structs.Vector[ring.Poly], mu string, kappa int) ring.Poly {
hasher := blake3.New()
buf := new(bytes.Buffer)
if _, err := A.WriteTo(buf); err != nil {
log.Fatalf("Error writing matrix A: %v\n", err)
}
if _, err := b.WriteTo(buf); err != nil {
log.Fatalf("Error writing vector b: %v\n", err)
}
if _, err := h.WriteTo(buf); err != nil {
log.Fatalf("Error writing vector h: %v\n", err)
}
binary.Write(buf, binary.BigEndian, []byte(mu))
hasher.Write(buf.Bytes())
hashOutput := hasher.Sum(nil)
prng, _ := sampling.NewKeyedPRNG(hashOutput[:keySize])
ternaryParams := ring.Ternary{H: kappa}
ternarySampler, err := ring.NewTernarySampler(prng, r, ternaryParams, false)
if err != nil {
log.Fatalf("Error creating ternary sampler: %v", err)
}
c := ternarySampler.ReadNew()
r.NTT(c, c)
r.MForm(c, c)
return c
}
// GenerateRandomSeed generates a random seed of length ell
func GenerateRandomSeed() []byte {
return utils.GetRandomBytes(keySize)
}
-145
View File
@@ -1,145 +0,0 @@
package primitives
import (
"crypto/rand"
"github.com/luxfi/corona/utils"
"math/big"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/structs"
)
// ShamirSecretSharing shares each coefficient of a vector of ring.Poly across k parties using (t, k)-threshold Shamir secret sharing.
func ShamirSecretSharingGeneral(r *ring.Ring, s []ring.Poly, t, k int) map[int]structs.Vector[ring.Poly] {
degree := r.N() // Number of coefficients in each ring.Poly
q := r.Modulus()
// Initialize shares for each party
shares := make(map[int]structs.Vector[ring.Poly], k)
for i := 0; i < k; i++ {
shares[i] = make([]ring.Poly, len(s))
for j := range shares[i] {
shares[i][j] = r.NewPoly()
}
}
for polyIndex, poly := range s {
coeffs := make([]*big.Int, degree)
r.PolyToBigint(poly, 1, coeffs)
for coeffIndex, secret := range coeffs {
polyCoeffs := make([]*big.Int, t)
polyCoeffs[0] = secret
for i := 1; i < t; i++ {
randomCoeff, _ := rand.Int(rand.Reader, q)
polyCoeffs[i] = randomCoeff
}
for i := 1; i <= k; i++ {
x := big.NewInt(int64(i))
shareValue := big.NewInt(0)
xPow := big.NewInt(1)
for _, coeff := range polyCoeffs {
term := new(big.Int).Mul(coeff, xPow)
shareValue.Add(shareValue, term)
shareValue.Mod(shareValue, q)
xPow.Mul(xPow, x)
}
if shares[i-1][polyIndex].Coeffs[0] == nil {
shares[i-1][polyIndex].Coeffs[0] = make([]uint64, degree)
}
shares[i-1][polyIndex].Coeffs[0][coeffIndex] = shareValue.Uint64()
}
}
}
return shares
}
// ShamirSecretSharing shares each coefficient of a vector of ring.Poly across k parties using (t, k)-threshold Shamir secret sharing. This optimized implementation only works when t = k.
func ShamirSecretSharing(r *ring.Ring, s []ring.Poly, k int, lambdas []ring.Poly) map[int]structs.Vector[ring.Poly] {
degree := r.N() // Number of coefficients in each ring.Poly
q := r.Modulus()
// Initialize shares for each party
shares := make(map[int]structs.Vector[ring.Poly], k)
for i := 0; i < k; i++ {
shares[i] = make([]ring.Poly, len(s))
for j := range shares[i] {
shares[i][j] = r.NewPoly()
}
}
for polyIndex, poly := range s {
coeffs := make([]*big.Int, degree)
r.PolyToBigint(poly, 1, coeffs)
for coeffIndex, secret := range coeffs {
randomShares := make([]*big.Int, k-1)
for i := 0; i < k-1; i++ {
randomShares[i] = utils.GetRandomInt(q)
}
sum := big.NewInt(0)
for i := 0; i < k-1; i++ {
lambdaCoeff := new(big.Int).SetUint64(lambdas[i].Coeffs[0][0])
shareTerm := new(big.Int).Mul(randomShares[i], lambdaCoeff)
sum.Add(sum, shareTerm)
sum.Mod(sum, q)
}
lastShare := new(big.Int).Sub(secret, sum)
lastShareLambdaCoeff := new(big.Int).SetUint64(lambdas[k-1].Coeffs[0][0])
lastShare.Mul(lastShare, new(big.Int).ModInverse(lastShareLambdaCoeff, q))
lastShare.Mod(lastShare, q)
for i := 0; i < k-1; i++ {
if shares[i][polyIndex].Coeffs[0] == nil {
shares[i][polyIndex].Coeffs[0] = make([]uint64, degree)
}
shares[i][polyIndex].Coeffs[0][coeffIndex] = randomShares[i].Uint64()
}
if shares[k-1][polyIndex].Coeffs[0] == nil {
shares[k-1][polyIndex].Coeffs[0] = make([]uint64, degree)
}
shares[k-1][polyIndex].Coeffs[0][coeffIndex] = lastShare.Uint64()
}
}
return shares
}
// ComputeLagrangeCoefficients computes the Lagrange coefficients for interpolation based on the indices of available shares.
func ComputeLagrangeCoefficients(r *ring.Ring, T []int, modulus *big.Int) []ring.Poly {
lagrangeCoefficients := make([]ring.Poly, len(T))
for i := 0; i < len(T); i++ {
xi := big.NewInt(int64(T[i] + 1))
numerator := big.NewInt(1)
denominator := big.NewInt(1)
for j := 0; j < len(T); j++ {
if i != j {
xj := big.NewInt(int64(T[j] + 1))
numerator.Mul(numerator, new(big.Int).Neg(xj))
numerator.Mod(numerator, modulus)
temp := new(big.Int).Sub(xi, xj)
denominator.Mul(denominator, temp)
denominator.Mod(denominator, modulus)
}
}
denomInv := new(big.Int).ModInverse(denominator, modulus)
coeff := new(big.Int).Mul(numerator, denomInv)
coeff.Mod(coeff, modulus)
lagrangePoly := r.NewPoly()
r.SetCoefficientsBigint([]*big.Int{coeff}, lagrangePoly)
lagrangeCoefficients[i] = lagrangePoly
}
return lagrangeCoefficients
}
-60
View File
@@ -1,60 +0,0 @@
package sign
import "github.com/luxfi/corona/config"
// PARAMETERS - Default values, can be overridden by configuration
var (
M = 8
N = 7
Dbar = 48
B = 430070539612332.205811372782969 // 2^48.61156663661591
Bsquare = "184960669042442604975662780477" // B^2
Kappa = 23
LogN = 8
SigmaE = 6.108187070284607
BoundE = SigmaE * 2
SigmaStar = 172852667880.2713189548230532887787 // 2^37.33075191469097
BoundStar = SigmaStar * 2
SigmaU = 163961331.5239387
BoundU = SigmaU * 2
KeySize = 32 // 256 bits
Q uint64 = 0x1000000004A01 // 48-bit NTT-friendly prime
QNu uint64 = 0x80000
QXi uint64 = 0x40000
TrustedDealerID = 0
CombinerID = 1
Xi = 30
Nu = 29
EtaEpsilon = 2.650104
)
// ApplyConfig updates parameters from configuration
func ApplyConfig(cfg *config.SignatureParams) {
if cfg == nil {
return
}
M = cfg.M
N = cfg.N
Dbar = cfg.Dbar
B = cfg.B
Kappa = cfg.Kappa
LogN = cfg.LogN
SigmaE = cfg.SigmaE
BoundE = SigmaE * 2
SigmaStar = cfg.SigmaStar
BoundStar = SigmaStar * 2
SigmaU = cfg.SigmaU
BoundU = SigmaU * 2
KeySize = cfg.KeySize
Q = cfg.Q
QNu = cfg.QNu
QXi = cfg.QXi
TrustedDealerID = cfg.TrustedDealerID
CombinerID = cfg.CombinerID
Xi = cfg.Xi
Nu = cfg.Nu
EtaEpsilon = cfg.EtaEpsilon
K = cfg.PartyCount
Threshold = cfg.Threshold
}
-171
View File
@@ -1,171 +0,0 @@
package sign
import (
"fmt"
"github.com/luxfi/corona/primitives"
"log"
"math/big"
"time"
"github.com/montanaflynn/stats"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/sampling"
"github.com/tuneinsight/lattigo/v5/utils/structs"
)
var K int
var Threshold int
// main function orchestrates the threshold signature protocol
func LocalRun(x int) {
var totalGenDuration, totalFinalizeDuration, totalVerifyDuration time.Duration
// Create maps to collect durations across all runs
totalSignRound1Durations := make(map[int]float64)
totalSignRound2PreprocessDurations := make(map[int]float64)
totalSignRound2Durations := make(map[int]float64)
for run := 0; run < x; run++ {
log.Println("RUN:", run)
var genDuration, finalizeDuration, verifyDuration time.Duration
randomKey := make([]byte, KeySize)
r, _ := ring.NewRing(1<<LogN, []uint64{Q})
r_xi, _ := ring.NewRing(1<<LogN, []uint64{QXi})
r_nu, _ := ring.NewRing(1<<LogN, []uint64{QNu})
prng, _ := sampling.NewKeyedPRNG(randomKey)
uniformSampler := ring.NewUniformSampler(prng, r)
trustedDealerKey := randomKey
parties := make([]*Party, K)
for i := range parties {
prng, _ := sampling.NewKeyedPRNG(randomKey)
uniformSampler := ring.NewUniformSampler(prng, r)
parties[i] = NewParty(i, r, r_xi, r_nu, uniformSampler)
}
// GEN: Generate secret shares, seeds, and MAC keys
T := make([]int, K) // Active parties
for i := 0; i < K; i++ {
T[i] = i
}
lagrangeCoeffs := primitives.ComputeLagrangeCoefficients(r, T, big.NewInt(int64(Q)))
log.Println("Gen")
start := time.Now()
A, skShares, seeds, MACKeys, b := Gen(r, r_xi, uniformSampler, trustedDealerKey, lagrangeCoeffs)
genDuration = time.Since(start)
log.Println("Gen Duration:", genDuration)
for partyID := 0; partyID < K; partyID++ {
parties[partyID].SkShare = skShares[partyID]
parties[partyID].Seed = seeds
parties[partyID].MACKeys = MACKeys[partyID]
}
// Create maps to collect durations for this run
signRound1Durations := make(map[int]time.Duration)
signRound2PreprocessDurations := make(map[int]time.Duration)
signRound2Durations := make(map[int]time.Duration)
// SIGNATURE ROUND 1
mu := "Message"
sid := 1
PRFKey := primitives.GenerateRandomSeed()
log.Println("Generating lagrange coefficients...")
D := make(map[int]structs.Matrix[ring.Poly])
MACs := make(map[int]map[int][]byte)
for _, partyID := range T {
r.NTT(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
r.MForm(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
parties[partyID].Lambda = lagrangeCoeffs[partyID]
parties[partyID].Seed = seeds
log.Println("Sign Round 1, party", partyID)
start = time.Now()
D[partyID], MACs[partyID] = parties[partyID].SignRound1(A, sid, []byte(PRFKey), T)
signRound1Durations[partyID] = time.Since(start)
}
// SIGNATURE ROUND 2
z := make(map[int]structs.Vector[ring.Poly])
for _, partyID := range T {
log.Println("Sign Round 2 preprocess, party", partyID)
start = time.Now()
valid, DSum, hash := parties[partyID].SignRound2Preprocess(A, b, D, MACs, sid, T)
if !valid {
log.Fatalf("MAC verification failed for party %d", partyID)
}
signRound2PreprocessDurations[partyID] = time.Since(start)
log.Println("Sign round 2 party", partyID)
start = time.Now()
z[partyID] = parties[partyID].SignRound2(A, b, DSum, sid, mu, T, []byte(PRFKey), hash)
signRound2Durations[partyID] = time.Since(start)
}
// SIGNATURE FINALIZE
log.Println("finalizing...")
finalParty := parties[0]
start = time.Now()
_, sig, Delta := finalParty.SignFinalize(z, A, b)
finalizeDuration = time.Since(start)
// Verify the signature
start = time.Now()
valid := Verify(r, r_xi, r_nu, sig, A, mu, b, finalParty.C, Delta)
verifyDuration = time.Since(start)
fmt.Printf("Signature Verification Result: %v\n", valid)
// Accumulate durations
totalGenDuration += genDuration
totalFinalizeDuration += finalizeDuration
totalVerifyDuration += verifyDuration
// Accumulate phase durations
for partyID, duration := range signRound1Durations {
totalSignRound1Durations[partyID] += float64(duration.Nanoseconds()) / 1e6
}
for partyID, duration := range signRound2PreprocessDurations {
totalSignRound2PreprocessDurations[partyID] += float64(duration.Nanoseconds()) / 1e6
}
for partyID, duration := range signRound2Durations {
totalSignRound2Durations[partyID] += float64(duration.Nanoseconds()) / 1e6
}
}
// Print averaged durations
fmt.Println("Averaged durations over", x, "runs:")
fmt.Printf("Gen duration: %.3f ms\n", float64(totalGenDuration.Nanoseconds())/1e6/float64(x))
fmt.Printf("Finalize duration: %.3f ms\n", float64(totalFinalizeDuration.Nanoseconds())/1e6/float64(x))
fmt.Printf("Verify duration: %.3f ms\n", float64(totalVerifyDuration.Nanoseconds())/1e6/float64(x))
// Calculate and print averaged statistics for each phase
printAveragedStats("Signature Round 1", totalSignRound1Durations, x)
printAveragedStats("Signature Round 2 Preprocess", totalSignRound2PreprocessDurations, x)
printAveragedStats("Signature Round 2", totalSignRound2Durations, x)
// Calculate and print total signing and offline durations
totalSigningDurations := make(map[int]float64)
for partyID := range totalSignRound1Durations {
totalSigningDurations[partyID] = totalSignRound1Durations[partyID] + totalSignRound2PreprocessDurations[partyID] + totalSignRound2Durations[partyID]
}
printAveragedStats("Total Signing", totalSigningDurations, x)
}
// printAveragedStats prints the mean, median, and standard deviation for a map of durations averaged over x runs
func printAveragedStats(phaseName string, totalDurations map[int]float64, x int) {
var values []float64
for _, totalDuration := range totalDurations {
values = append(values, totalDuration/float64(x))
}
mean, _ := stats.Mean(values)
median, _ := stats.Median(values)
stddev, _ := stats.StandardDeviation(values)
fmt.Printf("%s averaged duration stats over %d runs:\n", phaseName, x)
fmt.Printf(" Mean: %.3f ms\n", mean)
fmt.Printf(" Median: %.3f ms\n", median)
fmt.Printf(" Standard Deviation: %.3f ms\n", stddev)
}
-367
View File
@@ -1,367 +0,0 @@
package sign
import (
"bytes"
"log"
"math/big"
"github.com/luxfi/corona/primitives"
"github.com/luxfi/corona/utils"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/sampling"
"github.com/tuneinsight/lattigo/v5/utils/structs"
)
// Party struct holds all state and methods for a party in the protocol
type Party struct {
ID int
Ring *ring.Ring
RingXi *ring.Ring
RingNu *ring.Ring
UniformSampler *ring.UniformSampler
SkShare structs.Vector[ring.Poly]
Seed map[int][][]byte
R structs.Matrix[ring.Poly]
C ring.Poly
H structs.Vector[ring.Poly]
Lambda ring.Poly
D structs.Matrix[ring.Poly]
MACKeys map[int][]byte
MACs map[int][]byte
}
// NewParty initializes a new Party instance
func NewParty(id int, r *ring.Ring, r_xi *ring.Ring, r_nu *ring.Ring, sampler *ring.UniformSampler) *Party {
return &Party{
ID: id,
Ring: r,
RingXi: r_xi,
RingNu: r_nu,
UniformSampler: sampler,
MACKeys: make(map[int][]byte),
MACs: make(map[int][]byte),
}
}
// Gen generates the secret shares, seeds, MAC keys, and the public parameter b
func Gen(r *ring.Ring, r_xi *ring.Ring, uniformSampler *ring.UniformSampler, trustedDealerKey []byte, lagrangeCoefficients structs.Vector[ring.Poly]) (structs.Matrix[ring.Poly], map[int]structs.Vector[ring.Poly], map[int][][]byte, map[int]map[int][]byte, structs.Vector[ring.Poly]) {
A := utils.SamplePolyMatrix(r, M, N, uniformSampler, true, true)
precomputeSize := (K * K * KeySize) + (r.N() * N * (K - 1) * len(r.Modulus().Bytes())) + (K * (K - 1) * KeySize)
utils.PrecomputeRandomness(precomputeSize, trustedDealerKey)
prng, _ := sampling.NewKeyedPRNG(trustedDealerKey)
gaussianParams := ring.DiscreteGaussian{Sigma: SigmaE, Bound: BoundE}
gaussianSampler := ring.NewGaussianSampler(prng, r, gaussianParams, false)
s := utils.SamplePolyVector(r, N, gaussianSampler, false, false)
skShares := primitives.ShamirSecretSharing(r, s, Threshold, lagrangeCoefficients)
for _, skShare := range skShares {
utils.ConvertVectorToNTT(r, skShare)
}
utils.ConvertVectorToNTT(r, s)
e := utils.SamplePolyVector(r, M, gaussianSampler, true, true)
b := utils.InitializeVector(r, M)
utils.MatrixVectorMul(r, A, s, b)
utils.VectorAdd(r, b, e, b)
// Round b
utils.ConvertVectorFromNTT(r, b)
bTilde := utils.RoundVector(r, r_xi, b, Xi)
seeds := make(map[int][][]byte)
MACKeys := make(map[int]map[int][]byte)
MACKeys[0] = make(map[int][]byte)
for i := 0; i < K; i++ {
seeds[i] = make([][]byte, K)
for j := 0; j < K; j++ {
seeds[i][j] = utils.GetRandomBytes(KeySize)
if i != j {
if MACKeys[j] == nil {
MACKeys[j] = make(map[int][]byte)
}
if MACKeys[i][j] == nil && MACKeys[j][i] == nil {
MACKeys[i][j] = utils.GetRandomBytes(KeySize)
MACKeys[j][i] = MACKeys[i][j]
}
}
}
}
return A, skShares, seeds, MACKeys, bTilde
}
// SignRound1 performs the first round of signing
func (party *Party) SignRound1(A structs.Matrix[ring.Poly], sid int, PRFKey []byte, T []int) (structs.Matrix[ring.Poly], map[int][]byte) {
r := party.Ring
// Initialize r_star and e_star
skHash := primitives.PRNGKey(party.SkShare)
prng, _ := sampling.NewKeyedPRNG(skHash)
gaussianParams := ring.DiscreteGaussian{Sigma: SigmaStar, Bound: BoundStar}
gaussianSampler := ring.NewGaussianSampler(prng, r, gaussianParams, false)
r_star := utils.SamplePolyVector(r, N, gaussianSampler, true, true)
e_star := utils.SamplePolyVector(r, M, gaussianSampler, true, true)
// Initialize R_i and E_i
gaussianParams = ring.DiscreteGaussian{Sigma: SigmaE, Bound: BoundE}
gaussianSampler = ring.NewGaussianSampler(prng, r, gaussianParams, false)
R_i := utils.SamplePolyMatrix(r, N, Dbar, gaussianSampler, true, true)
E_i := utils.SamplePolyMatrix(r, M, Dbar, gaussianSampler, true, true)
concatenatedR := utils.InitializeMatrix(r, N, Dbar+1)
for i := range concatenatedR {
concatenatedR[i] = append([]ring.Poly{r_star[i]}, R_i[i]...)
}
party.R = concatenatedR
// Ensure concatenatedE is properly initialized
concatenatedE := utils.InitializeMatrix(r, M, Dbar+1)
for i := range concatenatedE {
concatenatedE[i] = append([]ring.Poly{e_star[i]}, E_i[i]...)
}
D := utils.InitializeMatrix(r, M, Dbar+1)
utils.MatrixMatrixMul(r, A, concatenatedR, D)
utils.MatrixAdd(r, concatenatedE, D, D)
party.D = D
// Generate MACs for each party
MACs := make(map[int][]byte)
for _, j := range T {
if j != party.ID {
MACs[j] = primitives.GenerateMAC(D, party.MACKeys[j], party.ID, sid, T, j, false)
}
}
return D, MACs
}
// SignRound2Preprocess verifies the MACs received in round 1 and performs the minimum eigenvalue check
func (party *Party) SignRound2Preprocess(A structs.Matrix[ring.Poly], b structs.Vector[ring.Poly], D map[int]structs.Matrix[ring.Poly], MACs map[int]map[int][]byte, sid int, T []int) (bool, structs.Matrix[ring.Poly], []byte) {
hash := primitives.Hash(A, b, D, sid, T)
for _, j := range T {
if j != party.ID {
MAC := MACs[j][party.ID]
expectedMAC := primitives.GenerateMAC(D[j], party.MACKeys[j], party.ID, sid, T, j, true)
if !bytes.Equal(MAC, expectedMAC) {
return false, nil, nil
}
}
}
DSum := utils.InitializeMatrix(party.Ring, M, Dbar+1)
for _, D_j := range D {
utils.MatrixAdd(party.Ring, D_j, DSum, DSum)
}
if !FullRankCheck(DSum, party.Ring) {
log.Fatalf("Failed full rank check! Aborting.")
}
return true, DSum, hash
}
// SignRound2 performs the second round of signing
func (party *Party) SignRound2(A structs.Matrix[ring.Poly], bTilde structs.Vector[ring.Poly], DSum structs.Matrix[ring.Poly], sid int, mu string, T []int, PRFKey []byte, hash []byte) structs.Vector[ring.Poly] {
r := party.Ring
r_nu := party.RingNu
partyID := party.ID
concatR := party.R
seeds := party.Seed
s_i := party.SkShare
lambda := party.Lambda
onePoly := r.NewMonomialXi(0)
r.NTT(onePoly, onePoly)
r.MForm(onePoly, onePoly)
u := structs.Vector[ring.Poly]{}
oneSlice := structs.Vector[ring.Poly]{onePoly}
if Dbar > 0 {
h_u := primitives.GaussianHash(r, hash, mu, SigmaU, BoundU, Dbar)
u = append(oneSlice, h_u...)
}
h := utils.InitializeVector(r, M)
utils.MatrixVectorMul(r, DSum, u, h)
utils.ConvertVectorFromNTT(r, h)
roundedH := utils.RoundVector(r, r_nu, h, Nu)
party.H = roundedH
c := primitives.LowNormHash(r, A, bTilde, roundedH, mu, Kappa)
party.C = c
seed_i := party.Seed[party.ID]
mask := utils.InitializeVector(r, N)
for _, j := range T {
mask_j := primitives.PRF(r, seed_i[j], PRFKey, mu, hash, N)
utils.VectorAdd(r, mask, mask_j, mask)
}
maskPrime := utils.InitializeVector(r, N)
for _, j := range T {
mask_j := primitives.PRF(r, seeds[j][partyID], PRFKey, mu, hash, N)
utils.VectorAdd(r, maskPrime, mask_j, maskPrime)
}
z_i := utils.InitializeVector(r, N)
utils.MatrixVectorMul(r, concatR, u, z_i)
utils.VectorAdd(r, z_i, maskPrime, z_i)
s_c_lambda := utils.InitializeVector(r, N)
utils.VectorPolyMul(r, s_i, lambda, s_c_lambda)
utils.VectorPolyMul(r, s_c_lambda, c, s_c_lambda)
utils.VectorAdd(r, z_i, s_c_lambda, z_i)
utils.VectorSub(r, z_i, mask, z_i)
return z_i
}
// SignFinalize finalizes the signature
func (party *Party) SignFinalize(z map[int]structs.Vector[ring.Poly], A structs.Matrix[ring.Poly], bTilde structs.Vector[ring.Poly]) (ring.Poly, structs.Vector[ring.Poly], structs.Vector[ring.Poly]) {
r := party.Ring
r_xi := party.RingXi
r_nu := party.RingNu
c := party.C
h := party.H
z_sum := utils.InitializeVector(r, N)
for _, z_j := range z {
utils.VectorAdd(r, z_sum, z_j, z_sum)
}
Az_bc := utils.InitializeVector(r, M)
utils.MatrixVectorMul(r, A, z_sum, Az_bc)
bc := utils.InitializeVector(r, M)
b := utils.RestoreVector(r, r_xi, bTilde, Xi)
utils.ConvertVectorToNTT(r, b)
utils.VectorPolyMul(r, b, c, bc)
utils.VectorSub(r, Az_bc, bc, Az_bc)
utils.ConvertVectorFromNTT(r, Az_bc)
roundedAz_bc := utils.RoundVector(r, r_nu, Az_bc, Nu)
Delta := utils.InitializeVector(r_nu, M)
utils.VectorSub(r_nu, h, roundedAz_bc, Delta)
return party.C, z_sum, Delta
}
// Verify verifies the correctness of the signature
func Verify(r *ring.Ring, r_xi *ring.Ring, r_nu *ring.Ring, z structs.Vector[ring.Poly], A structs.Matrix[ring.Poly], mu string, bTilde structs.Vector[ring.Poly], c ring.Poly, roundedDelta structs.Vector[ring.Poly]) bool {
Az_bc := utils.InitializeVector(r, M)
utils.MatrixVectorMul(r, A, z, Az_bc)
bc := utils.InitializeVector(r, M)
b := utils.RestoreVector(r, r_xi, bTilde, Xi)
utils.ConvertVectorToNTT(r, b)
utils.VectorPolyMul(r, b, c, bc)
utils.VectorSub(r, Az_bc, bc, Az_bc)
utils.ConvertVectorFromNTT(r, Az_bc)
roundedAz_bc := utils.RoundVector(r, r_nu, Az_bc, Nu)
Az_bc_Delta := utils.InitializeVector(r_nu, M)
utils.VectorAdd(r_nu, roundedAz_bc, roundedDelta, Az_bc_Delta)
computedC := primitives.LowNormHash(r, A, bTilde, Az_bc_Delta, mu, Kappa)
if !r.Equal(c, computedC) {
return false
}
Delta := utils.RestoreVector(r, r_nu, roundedDelta, Nu)
utils.ConvertVectorFromNTT(r, z)
return CheckL2Norm(r, Delta, z)
}
// CheckL2Norm checks if the L2 norm of the vector of Delta is less than or equal to Bsquare
func CheckL2Norm(r *ring.Ring, Delta structs.Vector[ring.Poly], z structs.Vector[ring.Poly]) bool {
sumSquares := big.NewInt(0)
qBig := new(big.Int).SetUint64(Q)
halfQ := new(big.Int).Div(qBig, big.NewInt(2))
DeltaCoeffsBigInt := make(structs.Vector[[]*big.Int], r.N())
for i, polyCoeffs := range Delta {
DeltaCoeffsBigInt[i] = make([]*big.Int, r.N())
r.PolyToBigint(polyCoeffs, 1, DeltaCoeffsBigInt[i])
}
for _, polyCoeffs := range DeltaCoeffsBigInt {
for _, coeff := range polyCoeffs {
if coeff.Cmp(halfQ) > 0 {
coeff.Sub(coeff, qBig)
}
coeffSquare := new(big.Int).Mul(coeff, coeff)
sumSquares.Add(sumSquares, coeffSquare)
}
}
zCoeffsBigInt := make(structs.Vector[[]*big.Int], r.N())
for i, polyCoeffs := range z {
zCoeffsBigInt[i] = make([]*big.Int, r.N())
r.PolyToBigint(polyCoeffs, 1, zCoeffsBigInt[i])
}
for _, polyCoeffs := range zCoeffsBigInt {
for _, coeff := range polyCoeffs {
if coeff.Cmp(halfQ) > 0 {
coeff.Sub(coeff, qBig)
}
coeffSquare := new(big.Int).Mul(coeff, coeff)
sumSquares.Add(sumSquares, coeffSquare)
}
}
log.Println("Sum of Squares:", sumSquares)
log.Println("Bsquare:", Bsquare)
Bsquare, _ := new(big.Int).SetString(Bsquare, 10)
return sumSquares.Cmp(Bsquare) <= 0
}
// FullRankCheck checks if the given matrix is full-rank, ignoring the first column
func FullRankCheck(D structs.Matrix[ring.Poly], r *ring.Ring) bool {
phi := r.N()
q := r.Modulus()
submatrices := make([][][]*big.Int, phi)
for i := range submatrices {
submatrices[i] = make([][]*big.Int, len(D))
for row := range submatrices[i] {
submatrices[i][row] = make([]*big.Int, len(D[0])-1)
}
}
for row := range D {
for col := 1; col < len(D[row]); col++ {
coeffs := make([]*big.Int, phi)
r.PolyToBigint(D[row][col], 1, coeffs)
for i := 0; i < phi; i++ {
coeff := coeffs[i].Mod(coeffs[i], q)
submatrices[i][row][col-1] = coeff
}
}
}
for i := range submatrices {
if !utils.GaussianEliminationModQ(submatrices[i], q) {
return false
}
}
return true
}
-39
View File
@@ -1,39 +0,0 @@
package utils
import (
"fmt"
"runtime"
)
// Error wraps an error with additional context
type Error struct {
Op string // operation
Kind string // category of error
Err error // underlying error
}
func (e *Error) Error() string {
if e.Err == nil {
return fmt.Sprintf("%s: %s", e.Op, e.Kind)
}
return fmt.Sprintf("%s: %s: %v", e.Op, e.Kind, e.Err)
}
// Unwrap returns the underlying error
func (e *Error) Unwrap() error {
return e.Err
}
// WrapError creates a new Error with context
func WrapError(op string, kind string, err error) error {
return &Error{Op: op, Kind: kind, Err: err}
}
// PanicHandler recovers from panics and converts them to errors
func PanicHandler(op string) {
if r := recover(); r != nil {
_, file, line, _ := runtime.Caller(2)
err := fmt.Errorf("panic in %s at %s:%d: %v", op, file, line, r)
panic(WrapError(op, "panic", err))
}
}
-157
View File
@@ -1,157 +0,0 @@
package utils
import (
"log"
"math/big"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/structs"
)
const logN = 8
func MulPolyNaive(r *ring.Ring, p1 ring.Poly, p2 ring.Poly, p3 ring.Poly) {
degree := 1 << logN // Since we are in a ring modulo x^256 + 1
q := r.Modulus()
// Initialize result slice with big.Ints set to zero
result := make([]*big.Int, degree)
for i := range result {
result[i] = big.NewInt(0)
}
p1Coeffs := make([]*big.Int, degree)
r.PolyToBigint(p1, 1, p1Coeffs)
p2Coeffs := make([]*big.Int, degree)
r.PolyToBigint(p2, 1, p2Coeffs)
// Polynomial multiplication (convolution)
for i := range p1Coeffs {
for j := range p2Coeffs {
coeff1 := p1Coeffs[i]
coeff2 := p2Coeffs[j]
temp := new(big.Int).Mul(coeff1, coeff2)
if i+j < degree {
// Multiply coefficients and add to the right place
result[i+j].Add(result[i+j], temp)
} else {
index := (i + j) % degree
if (((i+j)-((i+j)%degree))/(2<<logN))%2 == 0 {
// Wrap around due to cyclotomic ring, i+j >= degree
result[index].Add(result[index], temp)
} else {
// Wrap around due to cyclotomic ring, i+j >= degree
result[index].Sub(result[index], temp) // subtracting because of x^256 + 1
}
}
}
}
// Reduce each coefficient modulo q
for i := range result {
result[i].Mod(result[i], q)
}
r.SetCoefficientsBigint(result, p3)
}
// Sets p3 = p1 * p2 by first converting into NTT, multiplying coefficient-wise, and then converting out of NTT with INTT
func MulPolyNTT(r *ring.Ring, p1 ring.Poly, p2 ring.Poly, p3 ring.Poly) {
// Transform p1 and p2 to the NTT domain
r.NTT(p1, p1)
r.NTT(p2, p2)
p1Coeffs := make([]*big.Int, r.N())
p2Coeffs := make([]*big.Int, r.N())
p3Coeffs := make([]*big.Int, r.N())
r.PolyToBigint(p1, 1, p1Coeffs)
r.PolyToBigint(p2, 1, p2Coeffs)
// Perform coefficient-wise multiplication in the NTT domain
for i := 0; i < r.N(); i++ {
p3Coeffs[i] = new(big.Int).Mul(p1Coeffs[i], p2Coeffs[i])
p3Coeffs[i] = new(big.Int).Mod(p3Coeffs[i], r.Modulus())
}
r.SetCoefficientsBigint(p3Coeffs, p3)
// Transform the result back to the standard domain using INTT
r.INTT(p3, p3)
r.INTT(p1, p1)
r.INTT(p2, p2)
}
// MulCoeffsNTT performs coefficient-wise multiplication of two polynomials in the NTT domain.
func MulCoeffsNTT(r *ring.Ring, p1, p2, result ring.Poly) {
p1Coeffs := make([]*big.Int, r.N())
p2Coeffs := make([]*big.Int, r.N())
p3Coeffs := make([]*big.Int, r.N())
r.PolyToBigint(p1, 1, p1Coeffs)
r.PolyToBigint(p2, 1, p2Coeffs)
for i := 0; i < r.N(); i++ {
p3Coeffs[i] = new(big.Int).Mul(p1Coeffs[i], p2Coeffs[i])
p3Coeffs[i] = new(big.Int).Mod(p3Coeffs[i], r.Modulus())
}
r.SetCoefficientsBigint(p3Coeffs, result)
}
// NAIVE MULTIPLICATIONS
// MatrixVectorMul performs matrix-vector multiplication.
// It takes a matrix of ring.Poly pointers, a vector of ring.Poly pointers, and outputs the result in a given result vector.
func MatrixVectorMulNaive(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs.Vector[ring.Poly], result structs.Vector[ring.Poly]) {
for i := range M {
result[i] = r.NewPoly()
for j := range M[i] {
temp := r.NewPoly()
MulPolyNaive(r, M[i][j], vec[j], temp)
r.Add(result[i], temp, result[i])
}
}
}
// MatrixMatrixMul performs matrix-matrix multiplication.
// It takes two matrices of ring.Poly pointers, M1 of dimensions m x p and M2 of dimensions p x n,
// and outputs the result in a given result matrix of dimensions m x n.
func MatrixMatrixMulNaive(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
return
}
m := len(M1)
p := len(M1[0])
n := len(M2[0])
// Initialize the result matrix with zeros
for i := 0; i < m; i++ {
result[i] = make([]ring.Poly, n)
for j := 0; j < n; j++ {
result[i][j] = r.NewPoly()
}
}
// Perform matrix multiplication
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
for k := 0; k < p; k++ {
temp := r.NewPoly()
MulPolyNaive(r, M1[i][k], M2[k][j], temp)
r.Add(result[i][j], temp, result[i][j])
}
}
}
}
// VectorPolyMulNaive performs element-wise multiplication of a vector by a polynomial.
// It takes a vector of ring.Poly pointers, a single ring.Poly pointer, and outputs the result in a given result vector.
func VectorPolyMulNaive(r *ring.Ring, vec structs.Vector[ring.Poly], poly ring.Poly, result structs.Vector[ring.Poly]) {
for i := range vec {
MulPolyNaive(r, vec[i], poly, result[i]) // Multiply each vector element by the polynomial
}
}
-524
View File
@@ -1,524 +0,0 @@
package utils
import (
"fmt"
"log"
"math/big"
"strings"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/structs"
"github.com/zeebo/blake3"
)
// MatrixVectorMul performs matrix-vector multiplication.
func MatrixVectorMulNTT(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs.Vector[ring.Poly], result structs.Vector[ring.Poly]) {
// Convert all elements of the matrix and the vector to the NTT domain
ConvertMatrixToNTT(r, M)
ConvertVectorToNTT(r, vec)
// Perform the multiplications coefficient-wise
for i := range M {
result[i] = r.NewPoly()
for j := range (M)[i] {
r.MulCoeffsMontgomeryThenAdd(M[i][j], vec[j], result[i])
}
}
// Convert the result and all other polynomials back to the original domain
ConvertVectorFromNTT(r, result)
ConvertMatrixFromNTT(r, M)
ConvertVectorFromNTT(r, vec)
}
// MatrixMatrixMul performs matrix-matrix multiplication.
func MatrixMatrixMulNTT(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
return
}
m := len(M1)
p := len(M1[0]) // Assuming all rows in M1 are of the same length
n := len(M2[0]) // Assuming all rows in M2 are of the same length
// Convert all elements of M1 and M2 to the NTT domain
ConvertMatrixToNTT(r, M1)
ConvertMatrixToNTT(r, M2)
// Initialize the result matrix with zeros
for i := 0; i < m; i++ {
result[i] = make([]ring.Poly, n)
for j := 0; j < n; j++ {
result[i][j] = r.NewPoly()
}
}
temp := r.NewPoly()
// Perform matrix multiplication coefficient-wise
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
for k := 0; k < p; k++ {
// r.MForm(M1[i][k], temp)
r.MulCoeffsMontgomeryThenAdd(temp, M2[k][j], result[i][j])
}
}
}
// Convert the result and all other polynomials back to the original domain
ConvertMatrixFromNTT(r, result)
ConvertMatrixFromNTT(r, M1)
ConvertMatrixFromNTT(r, M2)
}
// VectorPolyMul performs element-wise multiplication of a vector by a polynomial.
func VectorPolyMulNTT(r *ring.Ring, vec structs.Vector[ring.Poly], poly ring.Poly, result structs.Vector[ring.Poly]) {
// Convert the polynomial to the NTT domain
r.NTT(poly, poly)
// Convert all elements of the vector to the NTT domain
ConvertVectorToNTT(r, vec)
ConvertVectorToNTT(r, result)
temp := r.NewPoly()
// Perform the multiplications coefficient-wise
for i := range vec {
r.MulCoeffsMontgomery(temp, poly, result[i])
}
// Convert the result and all other polynomials back to the original domain
ConvertVectorFromNTT(r, result)
ConvertVectorFromNTT(r, vec)
r.INTT(poly, poly)
}
// No included NTT
// MatrixVectorMul performs matrix-vector multiplication.
func MatrixVectorMul(r *ring.Ring, M structs.Matrix[ring.Poly], vec structs.Vector[ring.Poly], result structs.Vector[ring.Poly]) {
for i := range M {
for j := range M[i] {
r.MulCoeffsMontgomeryThenAdd(M[i][j], vec[j], result[i])
}
}
}
// MatrixMatrixMul performs matrix-matrix multiplication.
func MatrixMatrixMul(r *ring.Ring, M1, M2 structs.Matrix[ring.Poly], result structs.Matrix[ring.Poly]) {
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len((M1)[0]) != len(M2) {
log.Fatalf("Matrix dimensions are not compatible for multiplication.")
return
}
m := len(M1)
p := len(M1[0])
n := len(M2[0])
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
for k := 0; k < p; k++ {
r.MulCoeffsMontgomeryThenAdd(M1[i][k], M2[k][j], result[i][j])
}
}
}
}
// VectorPolyMul performs element-wise multiplication of a vector by a polynomial.
func VectorPolyMul(r *ring.Ring, vec structs.Vector[ring.Poly], poly ring.Poly, result structs.Vector[ring.Poly]) {
for i := range vec {
r.MulCoeffsMontgomery(vec[i], poly, result[i])
}
}
// MatrixAdd adds two matrices of ring.Poly element-wise and stores the result in a given result matrix.
func MatrixAdd(r *ring.Ring, M1, M2, result structs.Matrix[ring.Poly]) {
if M1 == nil || M2 == nil || len(M1) == 0 || len(M2) == 0 || len(M1) != len(M2) || len((M1)[0]) != len((M2)[0]) {
log.Fatalf("Matrix dimensions must match for element-wise addition.")
return
}
m := len(M1)
n := len(M1[0])
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
r.Add(M1[i][j], M2[i][j], result[i][j])
}
}
}
// VectorAdd adds two vectors of ring.Poly element-wise and stores the result in a result vector.
func VectorAdd(r *ring.Ring, v1, v2, result structs.Vector[ring.Poly]) {
for i := range v1 {
r.Add(v1[i], v2[i], result[i])
}
}
// VectorSub subtracts two vectors of ring.Poly element-wise and stores the result in a result vector.
func VectorSub(r *ring.Ring, v1, v2, result structs.Vector[ring.Poly]) {
for i := range v1 {
r.Sub(v1[i], v2[i], result[i])
}
}
// SAMPLER HELPERS
// SamplePolyVector samples a vector of polynomials of a given length using the provided sampler.
func SamplePolyVector(r *ring.Ring, length int, sampler ring.Sampler, NTT bool, montgomery bool) structs.Vector[ring.Poly] {
vector := structs.Vector[ring.Poly](make([]ring.Poly, length))
for i := 0; i < length; i++ {
vector[i] = sampler.ReadNew()
if NTT {
r.NTT(vector[i], vector[i])
}
if montgomery {
r.MForm(vector[i], vector[i])
}
}
return vector
}
// SamplePolyMatrix samples a matrix of polynomials with given dimensions (rows and cols) using the provided sampler.
func SamplePolyMatrix(r *ring.Ring, rows, cols int, sampler ring.Sampler, NTT bool, montgomery bool) structs.Matrix[ring.Poly] {
matrix := structs.Matrix[ring.Poly](make([][]ring.Poly, rows))
for i := 0; i < rows; i++ {
matrix[i] = make([]ring.Poly, cols)
for j := 0; j < cols; j++ {
matrix[i][j] = sampler.ReadNew()
if NTT {
r.NTT(matrix[i][j], matrix[i][j])
}
if montgomery {
r.MForm(matrix[i][j], matrix[i][j])
}
}
}
return matrix
}
// PRINT FUNCTIONS
func PrintMatrix(label string, matrix structs.Matrix[ring.Poly]) {
log.Println(label)
for i, row := range matrix {
for j, poly := range row {
log.Printf("[%d][%d]: %s\n", i, j, formatCoeffs(poly.Coeffs[0]))
}
}
}
func PrintVector(label string, vector structs.Vector[ring.Poly]) {
log.Println(label)
for i, poly := range vector {
log.Printf("[%d]: %s\n", i, formatCoeffs(poly.Coeffs[0]))
}
}
func PrintPolynomial(label string, poly ring.Poly) {
log.Println(label)
log.Printf("%s\n", formatCoeffs(poly.Coeffs[0]))
}
func formatCoeffs(coeffs []uint64) string {
var coeffStr []string
for _, coeff := range coeffs {
coeffStr = append(coeffStr, fmt.Sprintf("%v", coeff))
}
return strings.Join(coeffStr, ", ")
}
// NTT CONVERSION
// ConvertMatrixToNTT converts a matrix of polynomials to the NTT domain.
func ConvertMatrixToNTT(r *ring.Ring, M structs.Matrix[ring.Poly]) {
for i := range M {
for j := range M[i] {
r.NTT(M[i][j], M[i][j])
r.MForm(M[i][j], M[i][j])
}
}
}
// ConvertMatrixFromNTT converts a matrix of polynomials from the NTT domain back to the standard domain.
func ConvertMatrixFromNTT(r *ring.Ring, M structs.Matrix[ring.Poly]) {
for i := range M {
for j := range M[i] {
r.IMForm(M[i][j], M[i][j])
r.INTT(M[i][j], M[i][j])
}
}
}
// ConvertVectorToNTT converts a vector of polynomials to the NTT domain.
func ConvertVectorToNTT(r *ring.Ring, vec structs.Vector[ring.Poly]) {
for i := range vec {
r.NTT(vec[i], vec[i])
r.MForm(vec[i], vec[i])
}
}
// ConvertVectorFromNTT converts a vector of polynomials from the NTT domain back to the standard domain.
func ConvertVectorFromNTT(r *ring.Ring, vec structs.Vector[ring.Poly]) {
for i := range vec {
r.IMForm(vec[i], vec[i])
r.INTT(vec[i], vec[i])
}
}
// INITIALIZE HELPERS
// InitializeVector creates and returns a vector of the given length, initializing each element as a new polynomial.
func InitializeVector(r *ring.Ring, length int) structs.Vector[ring.Poly] {
vector := make(structs.Vector[ring.Poly], length)
for i := range vector {
vector[i] = r.NewPoly()
}
return vector
}
// InitializeMatrix creates and returns a matrix of the given dimensions, initializing each element as a new polynomial.
func InitializeMatrix(r *ring.Ring, rows, cols int) structs.Matrix[ring.Poly] {
matrix := make(structs.Matrix[ring.Poly], rows)
for i := range matrix {
matrix[i] = make([]ring.Poly, cols)
for j := range matrix[i] {
matrix[i][j] = r.NewPoly()
}
}
return matrix
}
// PrintBigIntVector prints a vector of slices of *big.Int elements
func PrintBigIntVector(label string, vector structs.Vector[[]*big.Int]) {
log.Println(label)
for i, slice := range vector {
log.Printf("[%d]: %s\n", i, FormatBigIntSlice(slice))
}
}
func FormatBigIntSlice(slice []*big.Int) string {
var sliceStr []string
for _, val := range slice {
sliceStr = append(sliceStr, val.String())
}
return strings.Join(sliceStr, ", ")
}
// UTIL FUNCTIONS FOR LAGRANGE INTERPOLATION TESTING
// ReconstructSecret reconstructs the secret using the shares and Lagrange coefficients.
func ReconstructSecret(r *ring.Ring, shares map[int]structs.Vector[ring.Poly], lagrangeCoeffs map[int]ring.Poly) structs.Vector[ring.Poly] {
reconstructed := make(structs.Vector[ring.Poly], len(shares[0]))
for i := range reconstructed {
reconstructed[i] = r.NewPoly()
for partyID, share := range shares {
temp := r.NewPoly()
MulPolyNTT(r, share[i], lagrangeCoeffs[partyID], temp)
r.Add(reconstructed[i], temp, reconstructed[i])
}
}
return reconstructed
}
// CompareSecrets compares the reconstructed secret with the original.
func CompareSecrets(r *ring.Ring, reconstructed, original structs.Vector[ring.Poly]) bool {
for i := range reconstructed {
if !r.Equal(reconstructed[i], original[i]) {
return false
}
}
return true
}
// Copy map helpers
func CopyMatrixMap(original map[int]structs.Matrix[ring.Poly]) map[int]structs.Matrix[ring.Poly] {
copy := make(map[int]structs.Matrix[ring.Poly])
for key, value := range original {
copy[key] = value
}
return copy
}
func CopyVectorMap(original map[int]structs.Vector[ring.Poly]) map[int]structs.Vector[ring.Poly] {
copy := make(map[int]structs.Vector[ring.Poly])
for key, value := range original {
copy[key] = value
}
return copy
}
// CalculateBetaDelta computes ((B * p) - q) / (2 * q) as a big.Int
func CalculateBetaDelta(p uint64, B float64, q uint64) *big.Int {
pInt := new(big.Int).SetUint64(p)
BInt := new(big.Int)
BInt.SetString(fmt.Sprintf("%.0f", B), 10)
qInt := new(big.Int).SetUint64(q)
Bp := new(big.Int).Mul(BInt, pInt)
BpSubQ := new(big.Int).Sub(Bp, qInt)
twoQ := new(big.Int).Mul(big.NewInt(2), qInt)
betaDelta := new(big.Int).Div(BpSubQ, twoQ)
log.Println("betaddelta", betaDelta)
return betaDelta
}
func PrintSignRepresentation(r *ring.Ring, poly ring.Poly, modulus uint64) {
qBig := new(big.Int).SetUint64(modulus)
halfQ := new(big.Int).Div(qBig, big.NewInt(2))
coeffs := make([]*big.Int, r.N())
r.PolyToBigint(poly, 1, coeffs)
for i := 0; i < len(coeffs); i++ {
if coeffs[i].Cmp(halfQ) > 0 {
coeffs[i].Sub(coeffs[i], qBig)
}
}
log.Println("Coeffs", coeffs)
}
func PrintSignRepresentationVector(r *ring.Ring, vec structs.Vector[ring.Poly], modulus uint64) {
for i := 0; i < len(vec); i++ {
PrintSignRepresentation(r, vec[i], modulus)
}
}
func PrintSignRepresentationMatrix(r *ring.Ring, matrix structs.Matrix[ring.Poly], modulus uint64) {
for i := 0; i < len(matrix); i++ {
PrintSignRepresentationVector(r, matrix[i], modulus)
}
}
// Rounding
// RoundCoefficients rounds each coefficient of the polynomial as specified
func RoundCoefficients(r *ring.Ring, newRing *ring.Ring, poly ring.Poly, roundingVal uint) ring.Poly {
roundedPoly := newRing.NewPoly()
for i := range poly.Coeffs[0] {
coeff := poly.Coeffs[0][i]
roundedCoeff := (coeff + (1 << (roundingVal - 1))) >> roundingVal
roundedPoly.Coeffs[0][i] = roundedCoeff
}
return roundedPoly
}
// RoundVector rounds each polynomial in the vector
func RoundVector(r *ring.Ring, newRing *ring.Ring, v structs.Vector[ring.Poly], roundingVal uint) structs.Vector[ring.Poly] {
roundedVector := InitializeVector(newRing, len(v))
for i := range v {
roundedVector[i] = RoundCoefficients(r, newRing, v[i], roundingVal)
}
return roundedVector
}
// RestoreCoefficients multiplies each coefficient of the polynomial by 2^roundingVal
func RestoreCoefficients(r *ring.Ring, newRing *ring.Ring, poly ring.Poly, roundingVal uint) ring.Poly {
restoredPoly := r.NewPoly()
for i := range poly.Coeffs[0] {
coeff := poly.Coeffs[0][i]
restoredCoeff := coeff << roundingVal
restoredPoly.Coeffs[0][i] = restoredCoeff
}
return restoredPoly
}
// RestoreVector multiplies each polynomial in the vector by 2^roundingVal
func RestoreVector(r *ring.Ring, newRing *ring.Ring, v structs.Vector[ring.Poly], roundingVal uint) structs.Vector[ring.Poly] {
restoredVector := InitializeVector(r, len(v))
for i := range v {
restoredVector[i] = RestoreCoefficients(r, newRing, v[i], roundingVal)
}
return restoredVector
}
// Global variable to hold precomputed randomness
var PrecomputedRandomness []byte
var RandomnessIndex int
// PrecomputeRandomness precomputes all necessary randomness and stores it in the global variable
func PrecomputeRandomness(size int, key []byte) {
RandomnessIndex = 0
hasher := blake3.New()
hasher.Write(key)
digest := hasher.Digest()
PrecomputedRandomness = make([]byte, size)
digest.Read(PrecomputedRandomness)
}
// GetRandomBytes returns the next n bytes of precomputed randomness
func GetRandomBytes(n int) []byte {
bytes := PrecomputedRandomness[RandomnessIndex : RandomnessIndex+n]
RandomnessIndex += n
return bytes
}
// GetRandomInt returns a random integer from the precomputed randomness
func GetRandomInt(q *big.Int) *big.Int {
randBytes := GetRandomBytes(len(q.Bytes()))
randInt := new(big.Int).SetBytes(randBytes)
return randInt.Mod(randInt, q)
}
// Convert to signed representation
func SignedRepresentation(coeffs []*big.Int, Q uint64) {
qBig := new(big.Int).SetUint64(Q)
halfQ := new(big.Int).Div(qBig, big.NewInt(2))
for i := 0; i < len(coeffs); i++ {
if coeffs[i].Cmp(halfQ) > 0 {
coeffs[i].Sub(coeffs[i], qBig)
}
}
}
// GaussianEliminationModQ performs Gaussian elimination mod q and checks if the matrix is full rank
func GaussianEliminationModQ(mat [][]*big.Int, q *big.Int) bool {
m := copyMat(mat)
rows := len(m)
cols := len(m[0])
for i := 0; i < rows; i++ {
foundPivot := false
var pivotIdx int
for j := 0; j < cols; j++ {
if m[i][j].Cmp(big.NewInt(0)) != 0 {
foundPivot = true
pivotIdx = j
break
}
}
if !foundPivot {
return false
}
invPivot := new(big.Int).ModInverse(m[i][pivotIdx], q)
for j := 0; j < cols; j++ {
m[i][j].Mul(m[i][j], invPivot).Mod(m[i][j], q)
}
for k := i + 1; k < rows; k++ {
if m[k][pivotIdx].Cmp(big.NewInt(0)) != 0 {
factor := new(big.Int).Set(m[k][pivotIdx])
for j := 0; j < cols; j++ {
m[k][j].Sub(m[k][j], new(big.Int).Mul(factor, m[i][j])).Mod(m[k][j], q)
}
}
}
}
return true
}
func copyMat(matBig [][]*big.Int) [][]*big.Int {
rows := len(matBig)
cols := len(matBig[0])
data := make([][]*big.Int, rows)
for i := 0; i < rows; i++ {
data[i] = make([]*big.Int, cols)
for j := 0; j < cols; j++ {
data[i][j] = new(big.Int).Set(matBig[i][j])
}
}
return data
}
+5 -12
View File
@@ -13,31 +13,24 @@ require (
github.com/ethereum/c-kzg-4844/v2 v2.1.1
github.com/google/gofuzz v1.2.0
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9
github.com/leanovate/gopter v0.2.11
github.com/luxfi/ids v1.0.1
github.com/mr-tron/base58 v1.2.0
github.com/stretchr/testify v1.10.0
github.com/supranational/blst v0.3.15
github.com/zeebo/blake3 v0.2.4
golang.org/x/crypto v0.40.0
golang.org/x/crypto v0.41.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.34.0
golang.org/x/sys v0.35.0
)
require (
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/bwesterb/go-ristretto v1.2.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace (
github.com/luxfi/crypto/lamport => ./lamport
github.com/luxfi/crypto/mldsa => ./mldsa
github.com/luxfi/crypto/mlkem => ./mlkem
github.com/luxfi/crypto/precompile => ./precompile
github.com/luxfi/crypto/slhdsa => ./slhdsa
github.com/luxfi/corona => ../corona
)
replace github.com/luxfi/corona => ../corona
+8 -8
View File
@@ -51,8 +51,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -191,10 +189,12 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9 h1:G8fshCtNb60L5IM2tuYD81uh6YQFqJ78MAGUCMks7Bg=
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9/go.mod h1:XWeSWo+UqzMi1uh/Td/gKlVHaPQjUj92s3omn7eccUM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -303,8 +303,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -457,8 +457,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+262 -310
View File
@@ -1,32 +1,16 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package mldsa provides ML-DSA (FIPS 204) digital signature algorithm
// This is a placeholder implementation for CI testing
// Package mldsa provides REAL ML-DSA (FIPS 204) implementation using circl
package mldsa
import (
"crypto"
"crypto/sha256"
"errors"
"io"
)
// Security parameters for ML-DSA (Module Lattice Digital Signature Algorithm)
const (
// ML-DSA-44 (Level 2 security)
MLDSA44PublicKeySize = 1312
MLDSA44PrivateKeySize = 2528
MLDSA44SignatureSize = 2420
// ML-DSA-65 (Level 3 security)
MLDSA65PublicKeySize = 1952
MLDSA65PrivateKeySize = 4000
MLDSA65SignatureSize = 3293
// ML-DSA-87 (Level 5 security)
MLDSA87PublicKeySize = 2592
MLDSA87PrivateKeySize = 4864
MLDSA87SignatureSize = 4595
"github.com/cloudflare/circl/sign/mldsa/mldsa44"
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/cloudflare/circl/sign/mldsa/mldsa87"
)
// Mode represents the ML-DSA parameter set
@@ -38,171 +22,298 @@ const (
MLDSA87 Mode = 5 // Level 5
)
// Security parameters for ML-DSA (Module Lattice Digital Signature Algorithm)
const (
// ML-DSA-44 (Level 2 security)
MLDSA44PublicKeySize = mldsa44.PublicKeySize
MLDSA44PrivateKeySize = mldsa44.PrivateKeySize
MLDSA44SignatureSize = mldsa44.SignatureSize
// ML-DSA-65 (Level 3 security)
MLDSA65PublicKeySize = mldsa65.PublicKeySize
MLDSA65PrivateKeySize = mldsa65.PrivateKeySize
MLDSA65SignatureSize = mldsa65.SignatureSize
// ML-DSA-87 (Level 5 security)
MLDSA87PublicKeySize = mldsa87.PublicKeySize
MLDSA87PrivateKeySize = mldsa87.PrivateKeySize
MLDSA87SignatureSize = mldsa87.SignatureSize
)
// PublicKey represents an ML-DSA public key
type PublicKey struct {
mode Mode
data []byte
key interface{} // Can be mldsa44.PublicKey, mldsa65.PublicKey, or mldsa87.PublicKey
}
// PrivateKey represents an ML-DSA private key
type PrivateKey struct {
PublicKey *PublicKey
data []byte
mode Mode
key interface{} // Can be mldsa44.PrivateKey, mldsa65.PrivateKey, or mldsa87.PrivateKey
}
// GenerateKey generates a new ML-DSA key pair
// GenerateKey generates a new ML-DSA key pair using REAL implementation
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
switch mode {
case MLDSA44:
pubKeySize = MLDSA44PublicKeySize
privKeySize = MLDSA44PrivateKeySize
case MLDSA65:
pubKeySize = MLDSA65PublicKeySize
privKeySize = MLDSA65PrivateKeySize
case MLDSA87:
pubKeySize = MLDSA87PublicKeySize
privKeySize = MLDSA87PrivateKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
return nil, err
}
// Derive public key from private key for consistency
// In real ML-DSA, public key is derived from private key seed
h := sha256.New()
h.Write(privBytes[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubBytes[i:end], hash)
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
}
// Sign creates a signature for the given message
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if priv == nil || priv.PublicKey == nil {
return nil, errors.New("private key is nil")
}
var sigSize int
switch priv.PublicKey.mode {
switch mode {
case MLDSA44:
sigSize = MLDSA44SignatureSize
pub, priv, err := mldsa44.GenerateKey(rand)
if err != nil {
return nil, err
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, nil
case MLDSA65:
sigSize = MLDSA65SignatureSize
pub, priv, err := mldsa65.GenerateKey(rand)
if err != nil {
return nil, err
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, nil
case MLDSA87:
sigSize = MLDSA87SignatureSize
pub, priv, err := mldsa87.GenerateKey(rand)
if err != nil {
return nil, err
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, nil
default:
return nil, errors.New("invalid ML-DSA mode")
}
// Placeholder: create deterministic signature that can be verified
// Start with hash of public key and message (what Verify expects)
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(message)
hash := h.Sum(nil)
signature := make([]byte, sigSize)
// Copy the hash to the beginning of signature
copy(signature[:32], hash)
// Fill rest with deterministic data based on private key
h.Reset()
h.Write(priv.data)
h.Write(message)
privHash := h.Sum(nil)
for i := 32; i < sigSize; i += len(privHash) {
end := i + len(privHash)
if end > sigSize {
end = sigSize
}
copy(signature[i:end], privHash)
h.Write(privHash) // Generate more data
privHash = h.Sum(nil)
}
return signature, nil
}
// Verify verifies a signature using the public key
// Sign creates a REAL signature for the given message
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if priv == nil {
return nil, errors.New("private key is nil")
}
switch priv.mode {
case MLDSA44:
if key, ok := priv.key.(*mldsa44.PrivateKey); ok {
return key.Sign(rand, message, opts)
}
case MLDSA65:
if key, ok := priv.key.(*mldsa65.PrivateKey); ok {
return key.Sign(rand, message, opts)
}
case MLDSA87:
if key, ok := priv.key.(*mldsa87.PrivateKey); ok {
return key.Sign(rand, message, opts)
}
}
return nil, errors.New("invalid key type")
}
// Verify verifies a REAL signature using the public key
func (pub *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool {
if pub == nil {
return false
}
var expectedSigSize int
// Use empty context (nil) for ML-DSA verification
switch pub.mode {
case MLDSA44:
expectedSigSize = MLDSA44SignatureSize
if key, ok := pub.key.(*mldsa44.PublicKey); ok {
return mldsa44.Verify(key, message, nil, signature) // nil context
}
case MLDSA65:
expectedSigSize = MLDSA65SignatureSize
if key, ok := pub.key.(*mldsa65.PublicKey); ok {
return mldsa65.Verify(key, message, nil, signature) // nil context
}
case MLDSA87:
expectedSigSize = MLDSA87SignatureSize
default:
return false
}
// Check signature size
if len(signature) != expectedSigSize {
return false
}
// Placeholder verification: recompute expected signature based on public key and message
// In real implementation, this would use lattice-based verification
h := sha256.New()
h.Write(pub.data)
h.Write(message)
expectedSigStart := h.Sum(nil)
// Check if first 32 bytes of signature match expected
// This is a simplified check for our placeholder
if len(signature) < 32 {
return false
}
// Compare first 32 bytes
for i := 0; i < 32; i++ {
if signature[i] != expectedSigStart[i] {
return false
if key, ok := pub.key.(*mldsa87.PublicKey); ok {
return mldsa87.Verify(key, message, nil, signature) // nil context
}
}
return true
return false
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
switch pub.mode {
case MLDSA44:
if key, ok := pub.key.(*mldsa44.PublicKey); ok {
data, _ := key.MarshalBinary()
return data
}
case MLDSA65:
if key, ok := pub.key.(*mldsa65.PublicKey); ok {
data, _ := key.MarshalBinary()
return data
}
case MLDSA87:
if key, ok := pub.key.(*mldsa87.PublicKey); ok {
data, _ := key.MarshalBinary()
return data
}
}
return nil
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
switch priv.mode {
case MLDSA44:
if key, ok := priv.key.(*mldsa44.PrivateKey); ok {
data, _ := key.MarshalBinary()
return data
}
case MLDSA65:
if key, ok := priv.key.(*mldsa65.PrivateKey); ok {
data, _ := key.MarshalBinary()
return data
}
case MLDSA87:
if key, ok := priv.key.(*mldsa87.PrivateKey); ok {
data, _ := key.MarshalBinary()
return data
}
}
return nil
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
switch mode {
case MLDSA44:
if len(data) != MLDSA44PublicKeySize {
return nil, errors.New("invalid public key size for ML-DSA-44")
}
var key mldsa44.PublicKey
if err := key.UnmarshalBinary(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
case MLDSA65:
if len(data) != MLDSA65PublicKeySize {
return nil, errors.New("invalid public key size for ML-DSA-65")
}
var key mldsa65.PublicKey
if err := key.UnmarshalBinary(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
case MLDSA87:
if len(data) != MLDSA87PublicKeySize {
return nil, errors.New("invalid public key size for ML-DSA-87")
}
var key mldsa87.PublicKey
if err := key.UnmarshalBinary(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
default:
return nil, errors.New("invalid ML-DSA mode")
}
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
switch mode {
case MLDSA44:
if len(data) != MLDSA44PrivateKeySize {
return nil, errors.New("invalid private key size for ML-DSA-44")
}
var privKey mldsa44.PrivateKey
if err := privKey.UnmarshalBinary(data); err != nil {
return nil, err
}
// Extract public key from private key
pubKey := privKey.Public().(*mldsa44.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
case MLDSA65:
if len(data) != MLDSA65PrivateKeySize {
return nil, errors.New("invalid private key size for ML-DSA-65")
}
var privKey mldsa65.PrivateKey
if err := privKey.UnmarshalBinary(data); err != nil {
return nil, err
}
pubKey := privKey.Public().(*mldsa65.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
case MLDSA87:
if len(data) != MLDSA87PrivateKeySize {
return nil, errors.New("invalid private key size for ML-DSA-87")
}
var privKey mldsa87.PrivateKey
if err := privKey.UnmarshalBinary(data); err != nil {
return nil, err
}
pubKey := privKey.Public().(*mldsa87.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
default:
return nil, errors.New("invalid ML-DSA mode")
}
}
// String returns the string representation of the mode
@@ -217,163 +328,4 @@ func (m Mode) String() string {
default:
return "Unknown"
}
}
// NewPrivateKey creates a new private key with the given mode
func NewPrivateKey(mode Mode) *PrivateKey {
return &PrivateKey{
PublicKey: NewPublicKey(mode),
data: make([]byte, getPrivateKeySize(mode)),
}
}
// NewPublicKey creates a new public key with the given mode
func NewPublicKey(mode Mode) *PublicKey {
return &PublicKey{
mode: mode,
data: make([]byte, getPublicKeySize(mode)),
}
}
// SetBytes sets the key data from bytes
func (sk *PrivateKey) SetBytes(data []byte) {
if sk.data == nil || len(sk.data) != len(data) {
sk.data = make([]byte, len(data))
}
copy(sk.data, data)
}
// SetBytes sets the key data from bytes
func (pk *PublicKey) SetBytes(data []byte) {
if pk.data == nil || len(pk.data) != len(data) {
pk.data = make([]byte, len(data))
}
copy(pk.data, data)
}
// IsDeterministic returns whether the signature scheme is deterministic
func (sk *PrivateKey) IsDeterministic() bool {
// ML-DSA uses randomized signing by default
// Can be made deterministic by using nil rand
return false
}
func getPrivateKeySize(mode Mode) int {
switch mode {
case MLDSA44:
return MLDSA44PrivateKeySize
case MLDSA65:
return MLDSA65PrivateKeySize
case MLDSA87:
return MLDSA87PrivateKeySize
default:
return 0
}
}
func getPublicKeySize(mode Mode) int {
switch mode {
case MLDSA44:
return MLDSA44PublicKeySize
case MLDSA65:
return MLDSA65PublicKeySize
case MLDSA87:
return MLDSA87PublicKeySize
default:
return 0
}
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case MLDSA44:
expectedSize = MLDSA44PublicKeySize
case MLDSA65:
expectedSize = MLDSA65PublicKeySize
case MLDSA87:
expectedSize = MLDSA87PublicKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
// Make a copy of the data
pubData := make([]byte, expectedSize)
copy(pubData, data)
return &PublicKey{
mode: mode,
data: pubData,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedPrivSize, expectedPubSize int
switch mode {
case MLDSA44:
expectedPrivSize = MLDSA44PrivateKeySize
expectedPubSize = MLDSA44PublicKeySize
case MLDSA65:
expectedPrivSize = MLDSA65PrivateKeySize
expectedPubSize = MLDSA65PublicKeySize
case MLDSA87:
expectedPrivSize = MLDSA87PrivateKeySize
expectedPubSize = MLDSA87PublicKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
if len(data) != expectedPrivSize {
return nil, errors.New("invalid private key size")
}
// Make a copy of private key data
privData := make([]byte, expectedPrivSize)
copy(privData, data)
// Derive public key from private key (same as GenerateKey)
h := sha256.New()
h.Write(privData[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, expectedPubSize)
// Fill public key with deterministic data
for i := 0; i < expectedPubSize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > expectedPubSize {
end = expectedPubSize
}
copy(pubData[i:end], hash)
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
data: pubData,
},
data: privData,
}, nil
}
}
+7 -39
View File
@@ -75,13 +75,9 @@ func GenerateKeyOptimized(rand io.Reader, mode Mode) (*PrivateKey, error) {
pubBytes := allBytes[privKeySize:]
derivePublicKeyOptimized(privBytes[:32], pubBytes)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
// This optimized version is not compatible with the current API
// Use the standard GenerateKey instead
return GenerateKey(rand, mode)
}
// Optimized public key derivation
@@ -135,38 +131,10 @@ func (priv *PrivateKey) OptimizedSign(rand io.Reader, message []byte, opts crypt
hashBuf := hashBufferPool.Get().([]byte)
defer hashBufferPool.Put(hashBuf)
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(message)
copy(hashBuf, h.Sum(nil))
// Copy hash to signature
copy(signature[:32], hashBuf)
// Fill rest with deterministic data
h.Reset()
h.Write(priv.data[:32])
h.Write(message)
deterministicBytes := h.Sum(nil)
for i := 32; i < sigSize; i += 32 {
end := i + 32
if end > sigSize {
end = sigSize
}
copy(signature[i:end], deterministicBytes)
if end < sigSize {
h.Reset()
h.Write(deterministicBytes)
h.Write([]byte{byte(i / 32)})
deterministicBytes = h.Sum(deterministicBytes[:0])
}
}
// Return a copy since we're returning the pooled buffer
result := make([]byte, sigSize)
copy(result, signature)
return result, nil
// This optimized version is not compatible with the current API
// Use the standard Sign method instead
defer putSignatureBuffer(signature)
return priv.Sign(rand, message, opts)
}
// BatchDSA provides batch signing operations
+379
View File
@@ -0,0 +1,379 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package mldsa provides ML-DSA (FIPS 204) digital signature algorithm
// This is a placeholder implementation for CI testing
package mldsa
import (
"crypto"
"crypto/sha256"
"errors"
"io"
)
// Security parameters for ML-DSA (Module Lattice Digital Signature Algorithm)
const (
// ML-DSA-44 (Level 2 security)
MLDSA44PublicKeySize = 1312
MLDSA44PrivateKeySize = 2528
MLDSA44SignatureSize = 2420
// ML-DSA-65 (Level 3 security)
MLDSA65PublicKeySize = 1952
MLDSA65PrivateKeySize = 4000
MLDSA65SignatureSize = 3293
// ML-DSA-87 (Level 5 security)
MLDSA87PublicKeySize = 2592
MLDSA87PrivateKeySize = 4864
MLDSA87SignatureSize = 4595
)
// Mode represents the ML-DSA parameter set
type Mode int
const (
MLDSA44 Mode = 2 // Level 2
MLDSA65 Mode = 3 // Level 3
MLDSA87 Mode = 5 // Level 5
)
// PublicKey represents an ML-DSA public key
type PublicKey struct {
mode Mode
data []byte
}
// PrivateKey represents an ML-DSA private key
type PrivateKey struct {
PublicKey *PublicKey
data []byte
}
// GenerateKey generates a new ML-DSA key pair
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
switch mode {
case MLDSA44:
pubKeySize = MLDSA44PublicKeySize
privKeySize = MLDSA44PrivateKeySize
case MLDSA65:
pubKeySize = MLDSA65PublicKeySize
privKeySize = MLDSA65PrivateKeySize
case MLDSA87:
pubKeySize = MLDSA87PublicKeySize
privKeySize = MLDSA87PrivateKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
return nil, err
}
// Derive public key from private key for consistency
// In real ML-DSA, public key is derived from private key seed
h := sha256.New()
h.Write(privBytes[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubBytes[i:end], hash)
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
}
// Sign creates a signature for the given message
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if priv == nil || priv.PublicKey == nil {
return nil, errors.New("private key is nil")
}
var sigSize int
switch priv.PublicKey.mode {
case MLDSA44:
sigSize = MLDSA44SignatureSize
case MLDSA65:
sigSize = MLDSA65SignatureSize
case MLDSA87:
sigSize = MLDSA87SignatureSize
default:
return nil, errors.New("invalid ML-DSA mode")
}
// Placeholder: create deterministic signature that can be verified
// Start with hash of public key and message (what Verify expects)
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(message)
hash := h.Sum(nil)
signature := make([]byte, sigSize)
// Copy the hash to the beginning of signature
copy(signature[:32], hash)
// Fill rest with deterministic data based on private key
h.Reset()
h.Write(priv.data)
h.Write(message)
privHash := h.Sum(nil)
for i := 32; i < sigSize; i += len(privHash) {
end := i + len(privHash)
if end > sigSize {
end = sigSize
}
copy(signature[i:end], privHash)
h.Write(privHash) // Generate more data
privHash = h.Sum(nil)
}
return signature, nil
}
// Verify verifies a signature using the public key
func (pub *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool {
if pub == nil {
return false
}
var expectedSigSize int
switch pub.mode {
case MLDSA44:
expectedSigSize = MLDSA44SignatureSize
case MLDSA65:
expectedSigSize = MLDSA65SignatureSize
case MLDSA87:
expectedSigSize = MLDSA87SignatureSize
default:
return false
}
// Check signature size
if len(signature) != expectedSigSize {
return false
}
// Placeholder verification: recompute expected signature based on public key and message
// In real implementation, this would use lattice-based verification
h := sha256.New()
h.Write(pub.data)
h.Write(message)
expectedSigStart := h.Sum(nil)
// Check if first 32 bytes of signature match expected
// This is a simplified check for our placeholder
if len(signature) < 32 {
return false
}
// Compare first 32 bytes
for i := 0; i < 32; i++ {
if signature[i] != expectedSigStart[i] {
return false
}
}
return true
}
// String returns the string representation of the mode
func (m Mode) String() string {
switch m {
case MLDSA44:
return "ML-DSA-44"
case MLDSA65:
return "ML-DSA-65"
case MLDSA87:
return "ML-DSA-87"
default:
return "Unknown"
}
}
// NewPrivateKey creates a new private key with the given mode
func NewPrivateKey(mode Mode) *PrivateKey {
return &PrivateKey{
PublicKey: NewPublicKey(mode),
data: make([]byte, getPrivateKeySize(mode)),
}
}
// NewPublicKey creates a new public key with the given mode
func NewPublicKey(mode Mode) *PublicKey {
return &PublicKey{
mode: mode,
data: make([]byte, getPublicKeySize(mode)),
}
}
// SetBytes sets the key data from bytes
func (sk *PrivateKey) SetBytes(data []byte) {
if sk.data == nil || len(sk.data) != len(data) {
sk.data = make([]byte, len(data))
}
copy(sk.data, data)
}
// SetBytes sets the key data from bytes
func (pk *PublicKey) SetBytes(data []byte) {
if pk.data == nil || len(pk.data) != len(data) {
pk.data = make([]byte, len(data))
}
copy(pk.data, data)
}
// IsDeterministic returns whether the signature scheme is deterministic
func (sk *PrivateKey) IsDeterministic() bool {
// ML-DSA uses randomized signing by default
// Can be made deterministic by using nil rand
return false
}
func getPrivateKeySize(mode Mode) int {
switch mode {
case MLDSA44:
return MLDSA44PrivateKeySize
case MLDSA65:
return MLDSA65PrivateKeySize
case MLDSA87:
return MLDSA87PrivateKeySize
default:
return 0
}
}
func getPublicKeySize(mode Mode) int {
switch mode {
case MLDSA44:
return MLDSA44PublicKeySize
case MLDSA65:
return MLDSA65PublicKeySize
case MLDSA87:
return MLDSA87PublicKeySize
default:
return 0
}
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case MLDSA44:
expectedSize = MLDSA44PublicKeySize
case MLDSA65:
expectedSize = MLDSA65PublicKeySize
case MLDSA87:
expectedSize = MLDSA87PublicKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
// Make a copy of the data
pubData := make([]byte, expectedSize)
copy(pubData, data)
return &PublicKey{
mode: mode,
data: pubData,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedPrivSize, expectedPubSize int
switch mode {
case MLDSA44:
expectedPrivSize = MLDSA44PrivateKeySize
expectedPubSize = MLDSA44PublicKeySize
case MLDSA65:
expectedPrivSize = MLDSA65PrivateKeySize
expectedPubSize = MLDSA65PublicKeySize
case MLDSA87:
expectedPrivSize = MLDSA87PrivateKeySize
expectedPubSize = MLDSA87PublicKeySize
default:
return nil, errors.New("invalid ML-DSA mode")
}
if len(data) != expectedPrivSize {
return nil, errors.New("invalid private key size")
}
// Make a copy of private key data
privData := make([]byte, expectedPrivSize)
copy(privData, data)
// Derive public key from private key (same as GenerateKey)
h := sha256.New()
h.Write(privData[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, expectedPubSize)
// Fill public key with deterministic data
for i := 0; i < expectedPubSize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > expectedPubSize {
end = expectedPubSize
}
copy(pubData[i:end], hash)
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
data: pubData,
},
data: privData,
}, nil
}
+312 -196
View File
@@ -1,279 +1,395 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package mlkem provides ML-KEM (FIPS 203) key encapsulation mechanism
// This is a placeholder implementation for CI testing
// Package mlkem provides REAL ML-KEM (FIPS 203) implementation using circl
package mlkem
import (
"crypto/sha256"
crypto_rand "crypto/rand"
"errors"
"io"
)
// Security parameters for ML-KEM (Module Lattice Key Encapsulation Mechanism)
const (
// ML-KEM-512 (Level 1 security)
MLKEM512PublicKeySize = 800
MLKEM512PrivateKeySize = 1632
MLKEM512CiphertextSize = 768
MLKEM512SharedSecretSize = 32
// ML-KEM-768 (Level 3 security)
MLKEM768PublicKeySize = 1184
MLKEM768PrivateKeySize = 2400
MLKEM768CiphertextSize = 1088
MLKEM768SharedSecretSize = 32
// ML-KEM-1024 (Level 5 security)
MLKEM1024PublicKeySize = 1568
MLKEM1024PrivateKeySize = 3168
MLKEM1024CiphertextSize = 1568
MLKEM1024SharedSecretSize = 32
"github.com/cloudflare/circl/kem/mlkem/mlkem512"
"github.com/cloudflare/circl/kem/mlkem/mlkem768"
"github.com/cloudflare/circl/kem/mlkem/mlkem1024"
)
// Mode represents the ML-KEM parameter set
type Mode int
const (
MLKEM512 Mode = iota + 1
MLKEM768
MLKEM1024
MLKEM512 Mode = iota // Level 1 security
MLKEM768 // Level 3 security
MLKEM1024 // Level 5 security
)
// Security parameters for ML-KEM (Module Lattice Key Encapsulation Mechanism)
const (
// ML-KEM-512 (Level 1 security)
MLKEM512PublicKeySize = mlkem512.PublicKeySize
MLKEM512PrivateKeySize = mlkem512.PrivateKeySize
MLKEM512CiphertextSize = mlkem512.CiphertextSize
MLKEM512SharedSecretSize = mlkem512.SharedKeySize
// ML-KEM-768 (Level 3 security)
MLKEM768PublicKeySize = mlkem768.PublicKeySize
MLKEM768PrivateKeySize = mlkem768.PrivateKeySize
MLKEM768CiphertextSize = mlkem768.CiphertextSize
MLKEM768SharedSecretSize = mlkem768.SharedKeySize
// ML-KEM-1024 (Level 5 security)
MLKEM1024PublicKeySize = mlkem1024.PublicKeySize
MLKEM1024PrivateKeySize = mlkem1024.PrivateKeySize
MLKEM1024CiphertextSize = mlkem1024.CiphertextSize
MLKEM1024SharedSecretSize = mlkem1024.SharedKeySize
)
// PublicKey represents an ML-KEM public key
type PublicKey struct {
mode Mode
data []byte
key interface{} // Can be mlkem512.PublicKey, mlkem768.PublicKey, or mlkem1024.PublicKey
}
// PrivateKey represents an ML-KEM private key
type PrivateKey struct {
PublicKey PublicKey
data []byte
PublicKey *PublicKey
mode Mode
key interface{} // Can be mlkem512.PrivateKey, mlkem768.PrivateKey, or mlkem1024.PrivateKey
}
// EncapsulationResult contains the ciphertext and shared secret
type EncapsulationResult struct {
Ciphertext []byte
SharedSecret []byte
}
// GenerateKeyPair generates a new ML-KEM key pair
func GenerateKeyPair(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
// GenerateKeyPair generates a new ML-KEM key pair using REAL implementation
func GenerateKeyPair(rand io.Reader, mode Mode) (*PrivateKey, *PublicKey, error) {
if rand == nil {
rand = crypto_rand.Reader
}
switch mode {
case MLKEM512:
pubKeySize = MLKEM512PublicKeySize
privKeySize = MLKEM512PrivateKeySize
case MLKEM768:
pubKeySize = MLKEM768PublicKeySize
privKeySize = MLKEM768PrivateKeySize
case MLKEM1024:
pubKeySize = MLKEM1024PublicKeySize
privKeySize = MLKEM1024PrivateKeySize
default:
return nil, errors.New("invalid ML-KEM mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
return nil, err
}
// Derive public key from private key deterministically
h := sha256.New()
h.Write(privBytes[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
pub, priv, err := mlkem512.GenerateKeyPair(rand)
if err != nil {
return nil, nil, err
}
copy(pubBytes[i:end], hash)
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, &PublicKey{
mode: mode,
key: pub,
}, nil
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
case MLKEM768:
pub, priv, err := mlkem768.GenerateKeyPair(rand)
if err != nil {
return nil, nil, err
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, &PublicKey{
mode: mode,
key: pub,
}, nil
case MLKEM1024:
pub, priv, err := mlkem1024.GenerateKeyPair(rand)
if err != nil {
return nil, nil, err
}
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pub,
},
mode: mode,
key: priv,
}, &PublicKey{
mode: mode,
key: pub,
}, nil
default:
return nil, nil, errors.New("invalid ML-KEM mode")
}
}
// Encapsulate generates a shared secret and ciphertext
func (pub *PublicKey) Encapsulate(rand io.Reader) (*EncapsulationResult, error) {
// Encapsulate generates a ciphertext and shared secret using REAL implementation
func (pub *PublicKey) Encapsulate(rand io.Reader) (ciphertext, sharedSecret []byte, err error) {
if pub == nil {
return nil, errors.New("public key is nil")
return nil, nil, errors.New("public key is nil")
}
if rand == nil {
rand = crypto_rand.Reader
}
var ctSize int
switch pub.mode {
case MLKEM512:
ctSize = MLKEM512CiphertextSize
if key, ok := pub.key.(*mlkem512.PublicKey); ok {
seed := make([]byte, mlkem512.EncapsulationSeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
ct := make([]byte, mlkem512.CiphertextSize)
ss := make([]byte, mlkem512.SharedKeySize)
key.EncapsulateTo(ct, ss, seed)
return ct, ss, nil
}
case MLKEM768:
ctSize = MLKEM768CiphertextSize
if key, ok := pub.key.(*mlkem768.PublicKey); ok {
seed := make([]byte, mlkem768.EncapsulationSeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
ct := make([]byte, mlkem768.CiphertextSize)
ss := make([]byte, mlkem768.SharedKeySize)
key.EncapsulateTo(ct, ss, seed)
return ct, ss, nil
}
case MLKEM1024:
ctSize = MLKEM1024CiphertextSize
default:
return nil, errors.New("invalid ML-KEM mode")
if key, ok := pub.key.(*mlkem1024.PublicKey); ok {
seed := make([]byte, mlkem1024.EncapsulationSeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
ct := make([]byte, mlkem1024.CiphertextSize)
ss := make([]byte, mlkem1024.SharedKeySize)
key.EncapsulateTo(ct, ss, seed)
return ct, ss, nil
}
}
// Placeholder: generate random ciphertext
ct := make([]byte, ctSize)
if _, err := io.ReadFull(rand, ct); err != nil {
return nil, err
}
// Placeholder: derive shared secret deterministically
// Use SHA256(pubkey || ciphertext) for consistency
h := sha256.New()
h.Write(pub.data)
h.Write(ct)
ss := h.Sum(nil)
// Store the ciphertext hash for later decapsulation
// In real implementation, this would be proper KEM
return &EncapsulationResult{
Ciphertext: ct,
SharedSecret: ss,
}, nil
return nil, nil, errors.New("invalid key type")
}
// Decapsulate recovers the shared secret from ciphertext
func (priv *PrivateKey) Decapsulate(ciphertext []byte) ([]byte, error) {
// Decapsulate recovers the shared secret from ciphertext using REAL implementation
func (priv *PrivateKey) Decapsulate(ciphertext []byte) (sharedSecret []byte, err error) {
if priv == nil {
return nil, errors.New("private key is nil")
}
var expectedCtSize int
switch priv.PublicKey.mode {
switch priv.mode {
case MLKEM512:
expectedCtSize = MLKEM512CiphertextSize
if key, ok := priv.key.(*mlkem512.PrivateKey); ok {
if len(ciphertext) != mlkem512.CiphertextSize {
return nil, errors.New("invalid ciphertext size for ML-KEM-512")
}
ss := make([]byte, mlkem512.SharedKeySize)
key.DecapsulateTo(ss, ciphertext)
return ss, nil
}
case MLKEM768:
expectedCtSize = MLKEM768CiphertextSize
if key, ok := priv.key.(*mlkem768.PrivateKey); ok {
if len(ciphertext) != mlkem768.CiphertextSize {
return nil, errors.New("invalid ciphertext size for ML-KEM-768")
}
ss := make([]byte, mlkem768.SharedKeySize)
key.DecapsulateTo(ss, ciphertext)
return ss, nil
}
case MLKEM1024:
expectedCtSize = MLKEM1024CiphertextSize
default:
return nil, errors.New("invalid ML-KEM mode")
if key, ok := priv.key.(*mlkem1024.PrivateKey); ok {
if len(ciphertext) != mlkem1024.CiphertextSize {
return nil, errors.New("invalid ciphertext size for ML-KEM-1024")
}
ss := make([]byte, mlkem1024.SharedKeySize)
key.DecapsulateTo(ss, ciphertext)
return ss, nil
}
}
if len(ciphertext) != expectedCtSize {
return nil, errors.New("invalid ciphertext size")
}
// Placeholder: derive shared secret deterministically
// Use same formula as Encapsulate: SHA256(pubkey || ciphertext)
// This ensures matching shared secrets
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(ciphertext)
ss := h.Sum(nil)
return ss, nil
return nil, errors.New("invalid key type")
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
switch pub.mode {
case MLKEM512:
if key, ok := pub.key.(*mlkem512.PublicKey); ok {
data := make([]byte, MLKEM512PublicKeySize)
key.Pack(data)
return data
}
case MLKEM768:
if key, ok := pub.key.(*mlkem768.PublicKey); ok {
data := make([]byte, MLKEM768PublicKeySize)
key.Pack(data)
return data
}
case MLKEM1024:
if key, ok := pub.key.(*mlkem1024.PublicKey); ok {
data := make([]byte, MLKEM1024PublicKeySize)
key.Pack(data)
return data
}
}
return nil
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
switch priv.mode {
case MLKEM512:
if key, ok := priv.key.(*mlkem512.PrivateKey); ok {
data := make([]byte, MLKEM512PrivateKeySize)
key.Pack(data)
return data
}
case MLKEM768:
if key, ok := priv.key.(*mlkem768.PrivateKey); ok {
data := make([]byte, MLKEM768PrivateKeySize)
key.Pack(data)
return data
}
case MLKEM1024:
if key, ok := priv.key.(*mlkem1024.PrivateKey); ok {
data := make([]byte, MLKEM1024PrivateKeySize)
key.Pack(data)
return data
}
}
return nil
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case MLKEM512:
expectedSize = MLKEM512PublicKeySize
if len(data) != MLKEM512PublicKeySize {
return nil, errors.New("invalid public key size for ML-KEM-512")
}
var key mlkem512.PublicKey
if err := key.Unpack(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
case MLKEM768:
expectedSize = MLKEM768PublicKeySize
if len(data) != MLKEM768PublicKeySize {
return nil, errors.New("invalid public key size for ML-KEM-768")
}
var key mlkem768.PublicKey
if err := key.Unpack(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
case MLKEM1024:
expectedSize = MLKEM1024PublicKeySize
if len(data) != MLKEM1024PublicKeySize {
return nil, errors.New("invalid public key size for ML-KEM-1024")
}
var key mlkem1024.PublicKey
if err := key.Unpack(data); err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
key: &key,
}, nil
default:
return nil, errors.New("invalid ML-KEM mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
return &PublicKey{
mode: mode,
data: data,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedPrivSize, expectedPubSize int
switch mode {
case MLKEM512:
expectedPrivSize = MLKEM512PrivateKeySize
expectedPubSize = MLKEM512PublicKeySize
if len(data) != MLKEM512PrivateKeySize {
return nil, errors.New("invalid private key size for ML-KEM-512")
}
var privKey mlkem512.PrivateKey
if err := privKey.Unpack(data); err != nil {
return nil, err
}
// Extract public key from private key
pubKey := privKey.Public().(*mlkem512.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
case MLKEM768:
expectedPrivSize = MLKEM768PrivateKeySize
expectedPubSize = MLKEM768PublicKeySize
if len(data) != MLKEM768PrivateKeySize {
return nil, errors.New("invalid private key size for ML-KEM-768")
}
var privKey mlkem768.PrivateKey
if err := privKey.Unpack(data); err != nil {
return nil, err
}
pubKey := privKey.Public().(*mlkem768.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
case MLKEM1024:
expectedPrivSize = MLKEM1024PrivateKeySize
expectedPubSize = MLKEM1024PublicKeySize
if len(data) != MLKEM1024PrivateKeySize {
return nil, errors.New("invalid private key size for ML-KEM-1024")
}
var privKey mlkem1024.PrivateKey
if err := privKey.Unpack(data); err != nil {
return nil, err
}
pubKey := privKey.Public().(*mlkem1024.PublicKey)
return &PrivateKey{
PublicKey: &PublicKey{
mode: mode,
key: pubKey,
},
mode: mode,
key: &privKey,
}, nil
default:
return nil, errors.New("invalid ML-KEM mode")
}
if len(data) != expectedPrivSize {
return nil, errors.New("invalid private key size")
}
// For placeholder: derive public key from private key deterministically
// Use first part of private key as seed for public key
h := sha256.New()
h.Write(data[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, expectedPubSize)
// Fill public key with deterministic data
for i := 0; i < expectedPubSize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > expectedPubSize {
end = expectedPubSize
}
copy(pubData[i:end], hash)
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubData,
},
data: data,
}, nil
}
// String returns the string representation of the mode
func (m Mode) String() string {
switch m {
case MLKEM512:
return "ML-KEM-512"
case MLKEM768:
return "ML-KEM-768"
case MLKEM1024:
return "ML-KEM-1024"
default:
return "Unknown"
}
}
+21 -14
View File
@@ -62,13 +62,18 @@ func GenerateKeyPairOptimized(rand io.Reader, mode Mode) (*PrivateKey, error) {
pubBytes := allBytes[privKeySize:]
derivePublicKeyOptimized(privBytes[:32], pubBytes)
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
// This optimized version is not compatible with the current API
// Use the standard GenerateKeyPair instead
_, pub, err := GenerateKeyPair(rand, mode)
if err != nil {
return nil, err
}
priv, _, err := GenerateKeyPair(rand, mode)
if err != nil {
return nil, err
}
priv.PublicKey = pub
return priv, nil
}
// Optimized public key derivation
@@ -109,7 +114,7 @@ type BatchKEM struct {
func NewBatchKEM(mode Mode, numKeys int) (*BatchKEM, error) {
keys := make([]*PrivateKey, numKeys)
for i := range keys {
key, err := GenerateKeyPair(rand.Reader, mode)
key, _, err := GenerateKeyPair(rand.Reader, mode)
if err != nil {
return nil, err
}
@@ -123,8 +128,9 @@ func NewBatchKEM(mode Mode, numKeys int) (*BatchKEM, error) {
}
// EncapsulateBatch performs batch encapsulation
func (b *BatchKEM) EncapsulateBatch(rand io.Reader) ([]*EncapsulationResult, error) {
results := make([]*EncapsulationResult, len(b.keys))
func (b *BatchKEM) EncapsulateBatch(rand io.Reader) ([][]byte, [][]byte, error) {
ciphertexts := make([][]byte, len(b.keys))
sharedSecrets := make([][]byte, len(b.keys))
// Process in parallel for better throughput
var wg sync.WaitGroup
@@ -134,12 +140,13 @@ func (b *BatchKEM) EncapsulateBatch(rand io.Reader) ([]*EncapsulationResult, err
wg.Add(1)
go func(idx int) {
defer wg.Done()
result, err := b.keys[idx].PublicKey.Encapsulate(rand)
ct, ss, err := b.keys[idx].PublicKey.Encapsulate(rand)
if err != nil {
errors[idx] = err
return
}
results[idx] = result
ciphertexts[idx] = ct
sharedSecrets[idx] = ss
}(i)
}
@@ -148,9 +155,9 @@ func (b *BatchKEM) EncapsulateBatch(rand io.Reader) ([]*EncapsulationResult, err
// Check for errors
for _, err := range errors {
if err != nil {
return nil, err
return nil, nil, err
}
}
return results, nil
return ciphertexts, sharedSecrets, nil
}
+279
View File
@@ -0,0 +1,279 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package mlkem provides ML-KEM (FIPS 203) key encapsulation mechanism
// This is a placeholder implementation for CI testing
package mlkem
import (
"crypto/sha256"
"errors"
"io"
)
// Security parameters for ML-KEM (Module Lattice Key Encapsulation Mechanism)
const (
// ML-KEM-512 (Level 1 security)
MLKEM512PublicKeySize = 800
MLKEM512PrivateKeySize = 1632
MLKEM512CiphertextSize = 768
MLKEM512SharedSecretSize = 32
// ML-KEM-768 (Level 3 security)
MLKEM768PublicKeySize = 1184
MLKEM768PrivateKeySize = 2400
MLKEM768CiphertextSize = 1088
MLKEM768SharedSecretSize = 32
// ML-KEM-1024 (Level 5 security)
MLKEM1024PublicKeySize = 1568
MLKEM1024PrivateKeySize = 3168
MLKEM1024CiphertextSize = 1568
MLKEM1024SharedSecretSize = 32
)
// Mode represents the ML-KEM parameter set
type Mode int
const (
MLKEM512 Mode = iota + 1
MLKEM768
MLKEM1024
)
// PublicKey represents an ML-KEM public key
type PublicKey struct {
mode Mode
data []byte
}
// PrivateKey represents an ML-KEM private key
type PrivateKey struct {
PublicKey PublicKey
data []byte
}
// EncapsulationResult contains the ciphertext and shared secret
type EncapsulationResult struct {
Ciphertext []byte
SharedSecret []byte
}
// GenerateKeyPair generates a new ML-KEM key pair
func GenerateKeyPair(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
switch mode {
case MLKEM512:
pubKeySize = MLKEM512PublicKeySize
privKeySize = MLKEM512PrivateKeySize
case MLKEM768:
pubKeySize = MLKEM768PublicKeySize
privKeySize = MLKEM768PrivateKeySize
case MLKEM1024:
pubKeySize = MLKEM1024PublicKeySize
privKeySize = MLKEM1024PrivateKeySize
default:
return nil, errors.New("invalid ML-KEM mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
return nil, err
}
// Derive public key from private key deterministically
h := sha256.New()
h.Write(privBytes[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubBytes[i:end], hash)
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
}
// Encapsulate generates a shared secret and ciphertext
func (pub *PublicKey) Encapsulate(rand io.Reader) (*EncapsulationResult, error) {
if pub == nil {
return nil, errors.New("public key is nil")
}
var ctSize int
switch pub.mode {
case MLKEM512:
ctSize = MLKEM512CiphertextSize
case MLKEM768:
ctSize = MLKEM768CiphertextSize
case MLKEM1024:
ctSize = MLKEM1024CiphertextSize
default:
return nil, errors.New("invalid ML-KEM mode")
}
// Placeholder: generate random ciphertext
ct := make([]byte, ctSize)
if _, err := io.ReadFull(rand, ct); err != nil {
return nil, err
}
// Placeholder: derive shared secret deterministically
// Use SHA256(pubkey || ciphertext) for consistency
h := sha256.New()
h.Write(pub.data)
h.Write(ct)
ss := h.Sum(nil)
// Store the ciphertext hash for later decapsulation
// In real implementation, this would be proper KEM
return &EncapsulationResult{
Ciphertext: ct,
SharedSecret: ss,
}, nil
}
// Decapsulate recovers the shared secret from ciphertext
func (priv *PrivateKey) Decapsulate(ciphertext []byte) ([]byte, error) {
if priv == nil {
return nil, errors.New("private key is nil")
}
var expectedCtSize int
switch priv.PublicKey.mode {
case MLKEM512:
expectedCtSize = MLKEM512CiphertextSize
case MLKEM768:
expectedCtSize = MLKEM768CiphertextSize
case MLKEM1024:
expectedCtSize = MLKEM1024CiphertextSize
default:
return nil, errors.New("invalid ML-KEM mode")
}
if len(ciphertext) != expectedCtSize {
return nil, errors.New("invalid ciphertext size")
}
// Placeholder: derive shared secret deterministically
// Use same formula as Encapsulate: SHA256(pubkey || ciphertext)
// This ensures matching shared secrets
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(ciphertext)
ss := h.Sum(nil)
return ss, nil
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case MLKEM512:
expectedSize = MLKEM512PublicKeySize
case MLKEM768:
expectedSize = MLKEM768PublicKeySize
case MLKEM1024:
expectedSize = MLKEM1024PublicKeySize
default:
return nil, errors.New("invalid ML-KEM mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
return &PublicKey{
mode: mode,
data: data,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedPrivSize, expectedPubSize int
switch mode {
case MLKEM512:
expectedPrivSize = MLKEM512PrivateKeySize
expectedPubSize = MLKEM512PublicKeySize
case MLKEM768:
expectedPrivSize = MLKEM768PrivateKeySize
expectedPubSize = MLKEM768PublicKeySize
case MLKEM1024:
expectedPrivSize = MLKEM1024PrivateKeySize
expectedPubSize = MLKEM1024PublicKeySize
default:
return nil, errors.New("invalid ML-KEM mode")
}
if len(data) != expectedPrivSize {
return nil, errors.New("invalid private key size")
}
// For placeholder: derive public key from private key deterministically
// Use first part of private key as seed for public key
h := sha256.New()
h.Write(data[:32]) // Use first 32 bytes as seed
h.Write([]byte("public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, expectedPubSize)
// Fill public key with deterministic data
for i := 0; i < expectedPubSize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > expectedPubSize {
end = expectedPubSize
}
copy(pubData[i:end], hash)
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubData,
},
data: data,
}, nil
}
+11 -39
View File
@@ -31,13 +31,10 @@ func GenerateKeyPairDRY(random io.Reader, mode Mode) (*PrivateKey, error) {
pubBytes := make([]byte, pubKeySize)
common.CopyWithPadding(pubBytes, common.DeriveKey(privBytes[:32], "public", pubKeySize), 0)
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
// This refactored version is not compatible with the current API
// Use the standard GenerateKeyPair instead
priv, _, err := GenerateKeyPair(random, mode)
return priv, err
}
// getKeySizes returns the key sizes for a given mode
@@ -69,42 +66,17 @@ func getCiphertextSize(mode Mode) int {
}
// EncapsulateDRY performs encapsulation using DRY principles
func (pub *PublicKey) EncapsulateDRY(random io.Reader) (*EncapsulationResult, error) {
if err := common.ValidateRandomSource(random); err != nil {
return nil, err
}
ctSize := getCiphertextSize(pub.mode)
if ctSize == 0 {
return nil, errors.New("invalid mode")
}
// Generate random ciphertext
ciphertext, err := common.GenerateRandomBytes(random, ctSize)
if err != nil {
return nil, err
}
// Derive shared secret
sharedSecret := common.DeriveKey(append(pub.data, ciphertext...), "shared", 32)
return &EncapsulationResult{
Ciphertext: ciphertext,
SharedSecret: sharedSecret,
}, nil
func (pub *PublicKey) EncapsulateDRY(random io.Reader) ([]byte, []byte, error) {
// This refactored version is not compatible with the current API
// Use the standard Encapsulate method instead
return pub.Encapsulate(random)
}
// DecapsulateDRY performs decapsulation using DRY principles
func (priv *PrivateKey) DecapsulateDRY(ciphertext []byte) ([]byte, error) {
expectedSize := getCiphertextSize(priv.PublicKey.mode)
if err := common.ValidateBufferSize(ciphertext, expectedSize, "ciphertext"); err != nil {
return nil, err
}
// Derive shared secret
sharedSecret := common.DeriveKey(append(priv.data, ciphertext...), "shared", 32)
return sharedSecret, nil
// This refactored version is not compatible with the current API
// Use the standard Decapsulate method instead
return priv.Decapsulate(ciphertext)
}
// SerializeDRY provides unified serialization
+5
View File
@@ -181,6 +181,11 @@ func (k *PrivateKey) Bytes() []byte {
return k.bytes
}
// ToECDSA returns the underlying ecdsa.PrivateKey
func (k *PrivateKey) ToECDSA() *ecdsa.PrivateKey {
return k.sk
}
// Address returns the address of the private key (via its public key)
func (k *PrivateKey) Address() ids.ShortID {
return k.PublicKey().Address()
+6
View File
@@ -0,0 +1,6 @@
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9 h1:G8fshCtNb60L5IM2tuYD81uh6YQFqJ78MAGUCMks7Bg=
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9/go.mod h1:XWeSWo+UqzMi1uh/Td/gKlVHaPQjUj92s3omn7eccUM=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+126 -232
View File
@@ -1,332 +1,226 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package slhdsa provides SLH-DSA (FIPS 205) stateless hash-based signatures
// This is a placeholder implementation for CI testing
// Package slhdsa provides REAL SLH-DSA (FIPS 205) implementation
package slhdsa
import (
"crypto"
"crypto/sha256"
"errors"
"io"
)
// Security parameters for SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
const (
// SLH-DSA-SHA2-128s (Small signatures, Level 1 security)
SLHDSA128sPublicKeySize = 32 // bytes
SLHDSA128sPrivateKeySize = 64 // bytes
SLHDSA128sSignatureSize = 7856 // bytes
// SLH-DSA-SHA2-128f (Fast signing, Level 1 security)
SLHDSA128fPublicKeySize = 32 // bytes
SLHDSA128fPrivateKeySize = 64 // bytes
SLHDSA128fSignatureSize = 17088 // bytes
// SLH-DSA-SHA2-192s (Small signatures, Level 3 security)
SLHDSA192sPublicKeySize = 48 // bytes
SLHDSA192sPrivateKeySize = 96 // bytes
SLHDSA192sSignatureSize = 16224 // bytes
// SLH-DSA-SHA2-192f (Fast signing, Level 3 security)
SLHDSA192fPublicKeySize = 48 // bytes
SLHDSA192fPrivateKeySize = 96 // bytes
SLHDSA192fSignatureSize = 35664 // bytes
// SLH-DSA-SHA2-256s (Small signatures, Level 5 security)
SLHDSA256sPublicKeySize = 64 // bytes
SLHDSA256sPrivateKeySize = 128 // bytes
SLHDSA256sSignatureSize = 29792 // bytes
// SLH-DSA-SHA2-256f (Fast signing, Level 5 security)
SLHDSA256fPublicKeySize = 64 // bytes
SLHDSA256fPrivateKeySize = 128 // bytes
SLHDSA256fSignatureSize = 49856 // bytes
"github.com/kasperdi/SPHINCSPLUS-golang/parameters"
"github.com/kasperdi/SPHINCSPLUS-golang/sphincs"
)
// Mode represents the SLH-DSA parameter set
type Mode int
const (
SLHDSA128s Mode = iota + 1
SLHDSA128f
SLHDSA192s
SLHDSA192f
SLHDSA256s
SLHDSA256f
SLHDSA128s Mode = iota + 1 // Small signatures, Level 1
SLHDSA128f // Fast signing, Level 1
SLHDSA192s // Small signatures, Level 3
SLHDSA192f // Fast signing, Level 3
SLHDSA256s // Small signatures, Level 5
SLHDSA256f // Fast signing, Level 5
)
// Security parameters for SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
const (
// SLH-DSA-SHA2-128s (Small signatures, Level 1 security)
SLHDSA128sPublicKeySize = 32
SLHDSA128sPrivateKeySize = 64
SLHDSA128sSignatureSize = 7856
// SLH-DSA-SHA2-128f (Fast signing, Level 1 security)
SLHDSA128fPublicKeySize = 32
SLHDSA128fPrivateKeySize = 64
SLHDSA128fSignatureSize = 17088
// SLH-DSA-SHA2-192s (Small signatures, Level 3 security)
SLHDSA192sPublicKeySize = 48
SLHDSA192sPrivateKeySize = 96
SLHDSA192sSignatureSize = 16224
// SLH-DSA-SHA2-192f (Fast signing, Level 3 security)
SLHDSA192fPublicKeySize = 48
SLHDSA192fPrivateKeySize = 96
SLHDSA192fSignatureSize = 35664
// SLH-DSA-SHA2-256s (Small signatures, Level 5 security)
SLHDSA256sPublicKeySize = 64
SLHDSA256sPrivateKeySize = 128
SLHDSA256sSignatureSize = 29792
// SLH-DSA-SHA2-256f (Fast signing, Level 5 security)
SLHDSA256fPublicKeySize = 64
SLHDSA256fPrivateKeySize = 128
SLHDSA256fSignatureSize = 49856
)
// PublicKey represents an SLH-DSA public key
type PublicKey struct {
mode Mode
data []byte
mode Mode
params *parameters.Parameters
key *sphincs.SPHINCS_PK
}
// PrivateKey represents an SLH-DSA private key
type PrivateKey struct {
PublicKey
data []byte
privateKey *sphincs.SPHINCS_SK
}
// GenerateKey generates a new SLH-DSA key pair
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
// getParams returns the SPHINCS+ parameters for the given mode
func getParams(mode Mode) (*parameters.Parameters, error) {
switch mode {
case SLHDSA128s:
pubKeySize = SLHDSA128sPublicKeySize
privKeySize = SLHDSA128sPrivateKeySize
return parameters.MakeSphincsPlusSHA256128sRobust(true), nil
case SLHDSA128f:
pubKeySize = SLHDSA128fPublicKeySize
privKeySize = SLHDSA128fPrivateKeySize
return parameters.MakeSphincsPlusSHA256128fRobust(true), nil
case SLHDSA192s:
pubKeySize = SLHDSA192sPublicKeySize
privKeySize = SLHDSA192sPrivateKeySize
return parameters.MakeSphincsPlusSHA256192sRobust(true), nil
case SLHDSA192f:
pubKeySize = SLHDSA192fPublicKeySize
privKeySize = SLHDSA192fPrivateKeySize
return parameters.MakeSphincsPlusSHA256192fRobust(true), nil
case SLHDSA256s:
pubKeySize = SLHDSA256sPublicKeySize
privKeySize = SLHDSA256sPrivateKeySize
return parameters.MakeSphincsPlusSHA256256sRobust(true), nil
case SLHDSA256f:
pubKeySize = SLHDSA256fPublicKeySize
privKeySize = SLHDSA256fPrivateKeySize
return parameters.MakeSphincsPlusSHA256256fRobust(true), nil
default:
return nil, errors.New("invalid SLH-DSA mode")
}
}
// Check for nil random source
// GenerateKey generates a new SLH-DSA key pair using REAL implementation
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random keys
// In real SLH-DSA, public key is derived from private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
params, err := getParams(mode)
if err != nil {
return nil, err
}
// Derive public key from private key for consistency
h := sha256.New()
h.Write(privBytes[:32]) // Use first part as seed
h.Write([]byte("slhdsa-public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubBytes[i:end], hash)
}
// Generate key pair using SPHINCS+ library
privKey, pubKey := sphincs.Spx_keygen(params)
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
mode: mode,
params: params,
key: pubKey,
},
data: privBytes,
privateKey: privKey,
}, nil
}
// Sign signs a message using the private key
// Sign signs a message using the REAL SPHINCS+ implementation
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if priv == nil {
if priv == nil || priv.privateKey == nil {
return nil, errors.New("private key is nil")
}
// Sign the message using SPHINCS+
signature := sphincs.Spx_sign(priv.params, message, priv.privateKey)
var sigSize int
switch priv.PublicKey.mode {
case SLHDSA128s:
sigSize = SLHDSA128sSignatureSize
case SLHDSA128f:
sigSize = SLHDSA128fSignatureSize
case SLHDSA192s:
sigSize = SLHDSA192sSignatureSize
case SLHDSA192f:
sigSize = SLHDSA192fSignatureSize
case SLHDSA256s:
sigSize = SLHDSA256sSignatureSize
case SLHDSA256f:
sigSize = SLHDSA256fSignatureSize
default:
return nil, errors.New("invalid SLH-DSA mode")
// Serialize the signature to bytes
sigBytes, err := signature.SerializeSignature()
if err != nil {
return nil, err
}
// Placeholder: create deterministic signature that can be verified
// Start with hash of public key and message (what Verify expects)
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(message)
hash := h.Sum(nil)
signature := make([]byte, sigSize)
// Copy the hash to beginning of signature
copy(signature[:32], hash)
// Fill rest with deterministic data based on private key
// SLH-DSA is stateless so signature should be deterministic
h.Reset()
h.Write(priv.data)
h.Write(message)
privHash := h.Sum(nil)
for i := 32; i < sigSize; i += len(privHash) {
end := i + len(privHash)
if end > sigSize {
end = sigSize
}
copy(signature[i:end], privHash)
h.Write(privHash) // Generate more data
privHash = h.Sum(nil)
}
return signature, nil
return sigBytes, nil
}
// Verify verifies a signature using the public key
// Verify verifies a signature using the REAL SPHINCS+ implementation
func (pub *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool {
if pub == nil {
return false
}
var expectedSigSize int
switch pub.mode {
case SLHDSA128s:
expectedSigSize = SLHDSA128sSignatureSize
case SLHDSA128f:
expectedSigSize = SLHDSA128fSignatureSize
case SLHDSA192s:
expectedSigSize = SLHDSA192sSignatureSize
case SLHDSA192f:
expectedSigSize = SLHDSA192fSignatureSize
case SLHDSA256s:
expectedSigSize = SLHDSA256sSignatureSize
case SLHDSA256f:
expectedSigSize = SLHDSA256fSignatureSize
default:
if pub == nil || pub.key == nil {
return false
}
// Check signature size
if len(signature) != expectedSigSize {
// Deserialize the signature
sig, err := sphincs.DeserializeSignature(pub.params, signature)
if err != nil {
return false
}
// Placeholder verification for SLH-DSA
// Recompute expected signature start based on public key and message
h := sha256.New()
h.Write(pub.data)
h.Write(message)
expectedSigStart := h.Sum(nil)
// Check if first 32 bytes match
if len(signature) < 32 {
return false
}
// Compare first 32 bytes
for i := 0; i < 32; i++ {
if signature[i] != expectedSigStart[i] {
return false
}
}
return true
// Verify using SPHINCS+
return sphincs.Spx_verify(pub.params, message, sig, pub.key)
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
if pub == nil || pub.key == nil {
return nil
}
bytes, _ := pub.key.SerializePK()
return bytes
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
if priv == nil || priv.privateKey == nil {
return nil
}
bytes, _ := priv.privateKey.SerializeSK()
return bytes
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case SLHDSA128s, SLHDSA128f:
expectedSize = SLHDSA128sPublicKeySize
case SLHDSA192s, SLHDSA192f:
expectedSize = SLHDSA192sPublicKeySize
case SLHDSA256s, SLHDSA256f:
expectedSize = SLHDSA256sPublicKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
params, err := getParams(mode)
if err != nil {
return nil, err
}
// Check size - SPHINCS+ public key is 2*N bytes
expectedSize := 2 * params.N
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
pubKey, err := sphincs.DeserializePK(params, data)
if err != nil {
return nil, err
}
return &PublicKey{
mode: mode,
data: data,
mode: mode,
params: params,
key: pubKey,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedSize int
var pubKeySize int
switch mode {
case SLHDSA128s, SLHDSA128f:
expectedSize = SLHDSA128sPrivateKeySize
pubKeySize = SLHDSA128sPublicKeySize
case SLHDSA192s, SLHDSA192f:
expectedSize = SLHDSA192sPrivateKeySize
pubKeySize = SLHDSA192sPublicKeySize
case SLHDSA256s, SLHDSA256f:
expectedSize = SLHDSA256sPrivateKeySize
pubKeySize = SLHDSA256sPublicKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
params, err := getParams(mode)
if err != nil {
return nil, err
}
// Check size - SPHINCS+ private key is 4*N bytes
expectedSize := 4 * params.N
if len(data) != expectedSize {
return nil, errors.New("invalid private key size")
}
// Derive public key from private key (same as GenerateKey)
h := sha256.New()
h.Write(data[:32]) // Use first part as seed
h.Write([]byte("slhdsa-public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubData[i:end], hash)
privKey, err := sphincs.DeserializeSK(params, data)
if err != nil {
return nil, err
}
// Create public key from private key components
pubKey := &sphincs.SPHINCS_PK{
PKseed: privKey.PKseed,
PKroot: privKey.PKroot,
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubData,
mode: mode,
params: params,
key: pubKey,
},
data: data,
privateKey: privKey,
}, nil
}
}
+4 -103
View File
@@ -55,113 +55,14 @@ func putHasher(h hash.Hash) {
// OptimizedGenerateKey generates keys with optimized memory usage
func OptimizedGenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
switch mode {
case SLHDSA128s:
pubKeySize = SLHDSA128sPublicKeySize
privKeySize = SLHDSA128sPrivateKeySize
case SLHDSA128f:
pubKeySize = SLHDSA128fPublicKeySize
privKeySize = SLHDSA128fPrivateKeySize
case SLHDSA192s:
pubKeySize = SLHDSA192sPublicKeySize
privKeySize = SLHDSA192sPrivateKeySize
case SLHDSA192f:
pubKeySize = SLHDSA192fPublicKeySize
privKeySize = SLHDSA192fPrivateKeySize
case SLHDSA256s:
pubKeySize = SLHDSA256sPublicKeySize
privKeySize = SLHDSA256sPrivateKeySize
case SLHDSA256f:
pubKeySize = SLHDSA256fPublicKeySize
privKeySize = SLHDSA256fPrivateKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Single allocation for both keys
totalSize := privKeySize + pubKeySize
allBytes := make([]byte, totalSize)
// Fill with random data
if _, err := io.ReadFull(rand, allBytes); err != nil {
return nil, err
}
// Split into public and private
pubBytes := allBytes[:pubKeySize]
privBytes := allBytes[pubKeySize:]
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
// Use the standard GenerateKey function which uses the proper SPHINCS+ library
return GenerateKey(rand, mode)
}
// OptimizedSign performs signing with buffer pooling
func (priv *PrivateKey) OptimizedSign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
var sigSize int
switch priv.PublicKey.mode {
case SLHDSA128s:
sigSize = SLHDSA128sSignatureSize
case SLHDSA128f:
sigSize = SLHDSA128fSignatureSize
case SLHDSA192s:
sigSize = SLHDSA192sSignatureSize
case SLHDSA192f:
sigSize = SLHDSA192fSignatureSize
case SLHDSA256s:
sigSize = SLHDSA256sSignatureSize
case SLHDSA256f:
sigSize = SLHDSA256fSignatureSize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
// Get buffer from pool for large signature
signature := getSlhBuffer(sigSize)
defer putSlhBuffer(signature)
// Use pooled hasher
h := getHasher()
defer putHasher(h)
// Deterministic signature generation
h.Write(priv.data[:32])
h.Write(message)
hash := h.Sum(nil)
// Build signature structure
copy(signature[:32], hash)
// Fill rest with deterministic expansion
for i := 32; i < sigSize; i += 32 {
h.Reset()
h.Write(hash)
h.Write([]byte{byte(i / 32)})
hash = h.Sum(hash[:0])
end := i + 32
if end > sigSize {
end = sigSize
}
copy(signature[i:end], hash)
}
// Return a copy since we're returning the pooled buffer
result := make([]byte, sigSize)
copy(result, signature)
return result, nil
// Use the standard Sign function which uses the proper SPHINCS+ library
return priv.Sign(rand, message, opts)
}
// MerkleTree represents an optimized Merkle tree for SLH-DSA
+332
View File
@@ -0,0 +1,332 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Package slhdsa provides SLH-DSA (FIPS 205) stateless hash-based signatures
// This is a placeholder implementation for CI testing
package slhdsa
import (
"crypto"
"crypto/sha256"
"errors"
"io"
)
// Security parameters for SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
const (
// SLH-DSA-SHA2-128s (Small signatures, Level 1 security)
SLHDSA128sPublicKeySize = 32 // bytes
SLHDSA128sPrivateKeySize = 64 // bytes
SLHDSA128sSignatureSize = 7856 // bytes
// SLH-DSA-SHA2-128f (Fast signing, Level 1 security)
SLHDSA128fPublicKeySize = 32 // bytes
SLHDSA128fPrivateKeySize = 64 // bytes
SLHDSA128fSignatureSize = 17088 // bytes
// SLH-DSA-SHA2-192s (Small signatures, Level 3 security)
SLHDSA192sPublicKeySize = 48 // bytes
SLHDSA192sPrivateKeySize = 96 // bytes
SLHDSA192sSignatureSize = 16224 // bytes
// SLH-DSA-SHA2-192f (Fast signing, Level 3 security)
SLHDSA192fPublicKeySize = 48 // bytes
SLHDSA192fPrivateKeySize = 96 // bytes
SLHDSA192fSignatureSize = 35664 // bytes
// SLH-DSA-SHA2-256s (Small signatures, Level 5 security)
SLHDSA256sPublicKeySize = 64 // bytes
SLHDSA256sPrivateKeySize = 128 // bytes
SLHDSA256sSignatureSize = 29792 // bytes
// SLH-DSA-SHA2-256f (Fast signing, Level 5 security)
SLHDSA256fPublicKeySize = 64 // bytes
SLHDSA256fPrivateKeySize = 128 // bytes
SLHDSA256fSignatureSize = 49856 // bytes
)
// Mode represents the SLH-DSA parameter set
type Mode int
const (
SLHDSA128s Mode = iota + 1
SLHDSA128f
SLHDSA192s
SLHDSA192f
SLHDSA256s
SLHDSA256f
)
// PublicKey represents an SLH-DSA public key
type PublicKey struct {
mode Mode
data []byte
}
// PrivateKey represents an SLH-DSA private key
type PrivateKey struct {
PublicKey
data []byte
}
// GenerateKey generates a new SLH-DSA key pair
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
var pubKeySize, privKeySize int
switch mode {
case SLHDSA128s:
pubKeySize = SLHDSA128sPublicKeySize
privKeySize = SLHDSA128sPrivateKeySize
case SLHDSA128f:
pubKeySize = SLHDSA128fPublicKeySize
privKeySize = SLHDSA128fPrivateKeySize
case SLHDSA192s:
pubKeySize = SLHDSA192sPublicKeySize
privKeySize = SLHDSA192sPrivateKeySize
case SLHDSA192f:
pubKeySize = SLHDSA192fPublicKeySize
privKeySize = SLHDSA192fPrivateKeySize
case SLHDSA256s:
pubKeySize = SLHDSA256sPublicKeySize
privKeySize = SLHDSA256sPrivateKeySize
case SLHDSA256f:
pubKeySize = SLHDSA256fPublicKeySize
privKeySize = SLHDSA256fPrivateKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
// Check for nil random source
if rand == nil {
return nil, errors.New("random source is nil")
}
// Placeholder implementation - generate random keys
// In real SLH-DSA, public key is derived from private key
privBytes := make([]byte, privKeySize)
if _, err := io.ReadFull(rand, privBytes); err != nil {
return nil, err
}
// Derive public key from private key for consistency
h := sha256.New()
h.Write(privBytes[:32]) // Use first part as seed
h.Write([]byte("slhdsa-public"))
pubSeed := h.Sum(nil)
pubBytes := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubBytes[i:end], hash)
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubBytes,
},
data: privBytes,
}, nil
}
// Sign signs a message using the private key
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if priv == nil {
return nil, errors.New("private key is nil")
}
var sigSize int
switch priv.PublicKey.mode {
case SLHDSA128s:
sigSize = SLHDSA128sSignatureSize
case SLHDSA128f:
sigSize = SLHDSA128fSignatureSize
case SLHDSA192s:
sigSize = SLHDSA192sSignatureSize
case SLHDSA192f:
sigSize = SLHDSA192fSignatureSize
case SLHDSA256s:
sigSize = SLHDSA256sSignatureSize
case SLHDSA256f:
sigSize = SLHDSA256fSignatureSize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
// Placeholder: create deterministic signature that can be verified
// Start with hash of public key and message (what Verify expects)
h := sha256.New()
h.Write(priv.PublicKey.data)
h.Write(message)
hash := h.Sum(nil)
signature := make([]byte, sigSize)
// Copy the hash to beginning of signature
copy(signature[:32], hash)
// Fill rest with deterministic data based on private key
// SLH-DSA is stateless so signature should be deterministic
h.Reset()
h.Write(priv.data)
h.Write(message)
privHash := h.Sum(nil)
for i := 32; i < sigSize; i += len(privHash) {
end := i + len(privHash)
if end > sigSize {
end = sigSize
}
copy(signature[i:end], privHash)
h.Write(privHash) // Generate more data
privHash = h.Sum(nil)
}
return signature, nil
}
// Verify verifies a signature using the public key
func (pub *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool {
if pub == nil {
return false
}
var expectedSigSize int
switch pub.mode {
case SLHDSA128s:
expectedSigSize = SLHDSA128sSignatureSize
case SLHDSA128f:
expectedSigSize = SLHDSA128fSignatureSize
case SLHDSA192s:
expectedSigSize = SLHDSA192sSignatureSize
case SLHDSA192f:
expectedSigSize = SLHDSA192fSignatureSize
case SLHDSA256s:
expectedSigSize = SLHDSA256sSignatureSize
case SLHDSA256f:
expectedSigSize = SLHDSA256fSignatureSize
default:
return false
}
// Check signature size
if len(signature) != expectedSigSize {
return false
}
// Placeholder verification for SLH-DSA
// Recompute expected signature start based on public key and message
h := sha256.New()
h.Write(pub.data)
h.Write(message)
expectedSigStart := h.Sum(nil)
// Check if first 32 bytes match
if len(signature) < 32 {
return false
}
// Compare first 32 bytes
for i := 0; i < 32; i++ {
if signature[i] != expectedSigStart[i] {
return false
}
}
return true
}
// Bytes returns the public key as bytes
func (pub *PublicKey) Bytes() []byte {
return pub.data
}
// Bytes returns the private key as bytes
func (priv *PrivateKey) Bytes() []byte {
return priv.data
}
// PublicKeyFromBytes reconstructs a public key from bytes
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
var expectedSize int
switch mode {
case SLHDSA128s, SLHDSA128f:
expectedSize = SLHDSA128sPublicKeySize
case SLHDSA192s, SLHDSA192f:
expectedSize = SLHDSA192sPublicKeySize
case SLHDSA256s, SLHDSA256f:
expectedSize = SLHDSA256sPublicKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid public key size")
}
return &PublicKey{
mode: mode,
data: data,
}, nil
}
// PrivateKeyFromBytes reconstructs a private key from bytes
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
var expectedSize int
var pubKeySize int
switch mode {
case SLHDSA128s, SLHDSA128f:
expectedSize = SLHDSA128sPrivateKeySize
pubKeySize = SLHDSA128sPublicKeySize
case SLHDSA192s, SLHDSA192f:
expectedSize = SLHDSA192sPrivateKeySize
pubKeySize = SLHDSA192sPublicKeySize
case SLHDSA256s, SLHDSA256f:
expectedSize = SLHDSA256sPrivateKeySize
pubKeySize = SLHDSA256sPublicKeySize
default:
return nil, errors.New("invalid SLH-DSA mode")
}
if len(data) != expectedSize {
return nil, errors.New("invalid private key size")
}
// Derive public key from private key (same as GenerateKey)
h := sha256.New()
h.Write(data[:32]) // Use first part as seed
h.Write([]byte("slhdsa-public"))
pubSeed := h.Sum(nil)
pubData := make([]byte, pubKeySize)
// Fill public key with deterministic data
for i := 0; i < pubKeySize; i += 32 {
h.Reset()
h.Write(pubSeed)
h.Write([]byte{byte(i / 32)})
hash := h.Sum(nil)
end := i + 32
if end > pubKeySize {
end = pubKeySize
}
copy(pubData[i:end], hash)
}
return &PrivateKey{
PublicKey: PublicKey{
mode: mode,
data: pubData,
},
data: data,
}, nil
}