mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Add ToECDSA method to PrivateKey
This commit is contained in:
-201
@@ -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.
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user