1 release (0 unstable)
new 1.0.0-beta | May 1, 2025 |
---|
#553 in Cryptography
2MB
7K
SLoC
Table of Contents
- Introduction
- Features
- Structure
- Installing, Testing, Benchmarks
- Crates structure
- Security
- Security Audit
- Summary of Changes After the Security Audit
- Contributing
- Reach out to us
Introduction
DKLs23 is a high-performance threshold ECDSA signing protocol with dynamic quorum management.
This is a production-ready, audited implementation that powers the Silent Shard SDK and has undergone a comprehensive security audit by Trail of Bits.
Features
- Distributed Key Generation (DKG)
- Distributed Signature Generation (DSG)
- Key refresh
- Import a singleton key and distribute it among parties
- Export a threshold key to a singleton one
- Quorum Change: change dynamically the set of participants adding or removing
- Migration: Migrate from compatible curve protocols like: GG** or CMP to DKLs23
Structure
The repository is structured as follows:
crates/
├── msg-relay/ # Message relay crate
└── dkls-metrics/ # Metrics crate
docs/ # Audit reports and whitepapers
examples/ # Examples (keygen, sign, refresh)
scripts/ # Utility scripts
src/
├── keygen/ # Key generation module
├── proto/ # Protocol definitions
├── setup/ # Setup module
├── sign/ # Signing module
├── key_export.rs # Key export functionality
├── key_import.rs # Key import functionality
├── lib.rs # Library entry point
├── pairs.rs # Key pairs functionality
├── proto.rs # Protocol implementation
└── setup.rs # Setup implementation
Installing, Testing, Benchmarks
Building
cargo build
Running Tests
cargo test
Documentation
Rustdoc reference is published here:
https://dkls23.silencelaboratories.com/docs/dkls23/
Benchmarks
Up-to-date benchmarks can be found here:
https://dkls23.silencelaboratories.com/
Criterion
cd crates/dkls-metrics/benches
cargo bench
Detailed Metrics (total message sizes sent and received)
cargo run -p dkls-metrics -r -- dkg --n 3 --t 2 --dsg
Examples
Under /examples/
directory there are examples on how to perform keygen, sign and refresh.
Running the examples:
cargo run --example keygen
cargo run --example sign
cargo run --example refresh
Crates structure
Protocols
Name | Reference | Code | Audited |
DKG | paper | code | Yes |
DSG | paper | code | Yes |
Refresh | - | code | Yes |
Import | - | code | No |
Export | - | code | No |
Quorum Change | reference | code | No |
Migration | reference | code | No |
Primitives
Name | Reference | Code | Audited |
1-2 OT | paper | code | Yes |
Base OT | paper | code | Yes |
Extended OT | paper | code | Yes |
Polynomial Arithmetics | - | code | Yes |
Matrix Arithmetics | - | code | Yes |
Verifiable Encryption | paper | code | WIP |
E2E Security
Name | Code | Audited |
Key Agreement: x25519+Curve25519 | code | Yes |
Authenticated Encryption: ChaCha20Poly1305 | code | Yes |
Sender authenticity: EdDSA+Curve25519 | code | Yes |
Security
If you discover a vulnerability, please follow the instructions in SECURITY.
Security Audit
Trail of Bits has performed a security audit in February, 2024 on the following commits:
The report is available here:
Summary of Changes After the Security Audit
Setup Messages
The run()
functions are now generic over the setup message type. All
setup message types must implement the trait ProtocolParticipant
,
which contains associated types that define how to sign and verify
broadcast messages.
ProtocolParticipant::MessageVerifier
is also used as a unique party
identifier. We use the identifier of the sender and receiver, if any,
to create a unique ID for each message. It is impossible to "parse" an
ID of the messages, but each party is able to calculate the ID of all
messages in a protocol.
Message Serialization
We implemented what we call zero-copy message serialization. We redefined all messages sent between parties and their components to be arrays of bytes or structures of arrays of bytes. This transformation allows us to safely cast a slice of bytes into a reference to some message structure if the sizes are equal.
This allows us to implement in-place message construction. We allocate a memory buffer of an appropriate size, take a mutable reference to some message structure, and pass it to a message constructor. Then we calculate the message signature or encrypt the message in place without any extra memory copying.
This provides not only memory efficiency but also more secure code because we have exactly one copy of secret material in memory and overwrite it with in-place encryption.
Key share representation also uses the same technique. We allocate a memory buffer for the key share at the beginning of the key generation execution and fill it piece by piece. This allows us to avoid extra memory copies.
Abstract networking layer
The run()
function is also generic over the so-called relay
. The
relay allows a party to send and receive messages. A message does not
contain explicit receiver-id or sender-id fields. A party does not
send a message to one or more receivers. We say that the party
publishes a message. Also, the party asks for a set of messages
"published" by other parties.
This "pull" pattern works better when some parties can't directly communicate with each other, and an additional intermediate service is required to facilitate message exchange.
If you have a two-party protocol, and the parties are connected by a two-way
communication channel, then implementation of Relay
could simply
drop ask messages.
There is a function message_receivers()
that allows you to build a message
map. This map will allow you to implement traditional message routing
with sender-id and receiver-id fields.
Contributing
Please refer to CONTRIBUTING.
Reach out to us
Don't hesitate to contact us if you need any assistance.
info@silencelaboratories.com security@silencelaboratories.com
Happy signing!
Dependencies
~9–16MB
~225K SLoC