Improve examples (#160)
* add trusted dealer example * add example for each ciphersuite-specific crate * simplify example * improve example; use ? instead of unwrap
This commit is contained in:
parent
6df6e32221
commit
7b83737137
93
README.md
93
README.md
|
@ -30,98 +30,5 @@ scratch. End-users should not use `frost-core` if they want to sign and verify s
|
|||
should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a
|
||||
dependency.
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
use frost_ristretto255::frost;
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let numsigners = 5;
|
||||
let threshold = 3;
|
||||
let (shares, pubkeys) =
|
||||
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
|
||||
|
||||
// Verifies the secret shares from the dealer
|
||||
let key_packages: Vec<frost::keys::KeyPackage> = shares
|
||||
.into_iter()
|
||||
.map(|share| frost::keys::KeyPackage::try_from(share).unwrap())
|
||||
.collect();
|
||||
|
||||
let mut nonces: HashMap<u16, Vec<frost::round1::SigningNonces>> = HashMap::new();
|
||||
let mut commitments: HashMap<u16, Vec<frost::round1::SigningCommitments>> = HashMap::new();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_index in 1..(threshold + 1) {
|
||||
// Generate one (1) nonce and one SigningCommitments instance for each
|
||||
// participant, up to _threshold_.
|
||||
let (nonce, commitment) = frost::round1::commit(participant_index as u16, &mut rng);
|
||||
nonces.insert(participant_index as u16, nonce);
|
||||
commitments.insert(participant_index as u16, commitment);
|
||||
}
|
||||
|
||||
// This is what the signature aggregator / coordinator needs to do:
|
||||
// - decide what message to sign
|
||||
// - take one (unused) commitment per signing participant
|
||||
let mut signature_shares: Vec<frost::round2::SignatureShare> = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().flatten().collect();
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_index in nonces.keys() {
|
||||
let key_package = key_packages
|
||||
.iter()
|
||||
.find(|key_package| *participant_index == key_package.index)
|
||||
.unwrap();
|
||||
|
||||
let nonces_to_use = nonces.get(participant_index).unwrap()[0];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share =
|
||||
frost::round2::sign(&signing_package, &nonces_to_use, key_package).unwrap();
|
||||
signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate (also verifies the signature shares)
|
||||
let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys);
|
||||
|
||||
assert!(group_signature_res.is_ok());
|
||||
|
||||
let group_signature = group_signature_res.unwrap();
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key).
|
||||
assert!(pubkeys
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key) from SharePackage.group_public
|
||||
for (participant_index, _) in nonces.clone() {
|
||||
let key_package = key_packages.get(participant_index as usize).unwrap();
|
||||
|
||||
assert!(key_package
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -15,5 +15,8 @@ released. The APIs and types in `frost-core` are subject to change.
|
|||
implementations for different ciphersuites / curves without having to implement all of FROST from
|
||||
scratch. End-users should not use `frost-core` if they want to sign and verify signatures, they
|
||||
should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a
|
||||
dependency, such as `frost-ristretto255`.
|
||||
dependency, such as [`frost_ristretto255`](../frost_ristretto255).
|
||||
|
||||
## Example
|
||||
|
||||
See ciphersuite-specific crates, e.g. [`frost_ristretto255`](../frost_ristretto255).
|
||||
|
|
|
@ -92,7 +92,7 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
|
|||
// This is what the signature aggregator / coordinator needs to do:
|
||||
// - decide what message to sign
|
||||
// - take one (unused) commitment per signing participant
|
||||
let mut signature_shares: Vec<frost::round2::SignatureShare<C>> = Vec::new();
|
||||
let mut signature_shares = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||
|
|
|
@ -1,32 +1,98 @@
|
|||
An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers
|
||||
An implementation of Schnorr signatures on the Ed25519 curve for both single and threshold numbers
|
||||
of signers (FROST).
|
||||
|
||||
## Examples
|
||||
## Example: key generation with trusted dealer and FROST signing
|
||||
|
||||
Creating a key with a trusted dealer and splitting into shares; then signing a message
|
||||
and aggregating the signature. Note that the example just simulates a distributed
|
||||
scenario in a single thread and it abstracts away any communication between peers.
|
||||
|
||||
Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the
|
||||
signature:
|
||||
|
||||
```rust
|
||||
use frost_ed25519 as frost;
|
||||
use rand::thread_rng;
|
||||
use frost_ed25519::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let msg = b"Hello!";
|
||||
let mut rng = thread_rng();
|
||||
let max_signers = 5;
|
||||
let min_signers = 3;
|
||||
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
|
||||
|
||||
// Generate a secret key and sign the message
|
||||
let sk = SigningKey::new(thread_rng());
|
||||
let sig = sk.sign(thread_rng(), msg);
|
||||
// Verifies the secret shares from the dealer and store them in a HashMap.
|
||||
// In practice, the KeyPackages must be sent to its respective participants
|
||||
// through a confidential and authenticated channel.
|
||||
let key_packages: HashMap<_, _> = shares
|
||||
.into_iter()
|
||||
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes`
|
||||
let sig_bytes = sig.to_bytes();
|
||||
let pk_bytes = VerifyingKey::from(&sk).to_bytes();
|
||||
let mut nonces = HashMap::new();
|
||||
let mut commitments = HashMap::new();
|
||||
|
||||
// Deserialize and verify the signature.
|
||||
let sig = Signature::from_bytes(sig_bytes)?;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
assert!(
|
||||
VerifyingKey::from_bytes(pk_bytes)
|
||||
.and_then(|pk| pk.verify(msg, &sig))
|
||||
.is_ok()
|
||||
);
|
||||
# Ok::<(), Error>(())
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_index in 1..(min_signers as u16 + 1) {
|
||||
let participant_identifier = participant_index.try_into().expect("should be nonzero");
|
||||
// Generate one (1) nonce and one SigningCommitments instance for each
|
||||
// participant, up to _threshold_.
|
||||
let (nonce, commitment) = frost::round1::commit(
|
||||
participant_identifier,
|
||||
key_packages[&participant_identifier].secret_share(),
|
||||
&mut rng,
|
||||
);
|
||||
// In practice, the nonces and commitment must be sent to the coordinator
|
||||
// (or to every other participant if there is no coordinator) using
|
||||
// an authenticated channel.
|
||||
nonces.insert(participant_identifier, nonce);
|
||||
commitments.insert(participant_identifier, commitment);
|
||||
}
|
||||
|
||||
// This is what the signature aggregator / coordinator needs to do:
|
||||
// - decide what message to sign
|
||||
// - take one (unused) commitment per signing participant
|
||||
let mut signature_shares = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
// In practice, the SigningPackage must be sent to all participants
|
||||
// involved in the current signing (at least min_signers participants),
|
||||
// using an authenticate channel (and confidential if the message is secret).
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_identifier in nonces.keys() {
|
||||
let key_package = &key_packages[participant_identifier];
|
||||
|
||||
let nonces_to_use = &nonces[participant_identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
|
||||
|
||||
// In practice, the signature share must be sent to the Coordinator
|
||||
// using an authenticated channel.
|
||||
signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate (also verifies the signature shares)
|
||||
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key).
|
||||
assert!(pubkeys
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
|
||||
# Ok::<(), frost::Error>(())
|
||||
```
|
||||
|
|
|
@ -1,32 +1,98 @@
|
|||
An implementation of Schnorr signatures on the P-256 curve for both single and threshold numbers
|
||||
of signers (FROST).
|
||||
|
||||
## Examples
|
||||
## Example: key generation with trusted dealer and FROST signing
|
||||
|
||||
Creating a key with a trusted dealer and splitting into shares; then signing a message
|
||||
and aggregating the signature. Note that the example just simulates a distributed
|
||||
scenario in a single thread and it abstracts away any communication between peers.
|
||||
|
||||
Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the
|
||||
signature:
|
||||
|
||||
```rust
|
||||
use frost_p256 as frost;
|
||||
use rand::thread_rng;
|
||||
use frost_p256::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let msg = b"Hello!";
|
||||
let mut rng = thread_rng();
|
||||
let max_signers = 5;
|
||||
let min_signers = 3;
|
||||
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
|
||||
|
||||
// Generate a secret key and sign the message
|
||||
let sk = SigningKey::new(thread_rng());
|
||||
let sig = sk.sign(thread_rng(), msg);
|
||||
// Verifies the secret shares from the dealer and store them in a HashMap.
|
||||
// In practice, the KeyPackages must be sent to its respective participants
|
||||
// through a confidential and authenticated channel.
|
||||
let key_packages: HashMap<_, _> = shares
|
||||
.into_iter()
|
||||
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes`
|
||||
let sig_bytes = sig.to_bytes();
|
||||
let pk_bytes = VerifyingKey::from(&sk).to_bytes();
|
||||
let mut nonces = HashMap::new();
|
||||
let mut commitments = HashMap::new();
|
||||
|
||||
// Deserialize and verify the signature.
|
||||
let sig = Signature::from_bytes(sig_bytes)?;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
assert!(
|
||||
VerifyingKey::from_bytes(pk_bytes)
|
||||
.and_then(|pk| pk.verify(msg, &sig))
|
||||
.is_ok()
|
||||
);
|
||||
# Ok::<(), Error>(())
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_index in 1..(min_signers as u16 + 1) {
|
||||
let participant_identifier = participant_index.try_into().expect("should be nonzero");
|
||||
// Generate one (1) nonce and one SigningCommitments instance for each
|
||||
// participant, up to _threshold_.
|
||||
let (nonce, commitment) = frost::round1::commit(
|
||||
participant_identifier,
|
||||
key_packages[&participant_identifier].secret_share(),
|
||||
&mut rng,
|
||||
);
|
||||
// In practice, the nonces and commitment must be sent to the coordinator
|
||||
// (or to every other participant if there is no coordinator) using
|
||||
// an authenticated channel.
|
||||
nonces.insert(participant_identifier, nonce);
|
||||
commitments.insert(participant_identifier, commitment);
|
||||
}
|
||||
|
||||
// This is what the signature aggregator / coordinator needs to do:
|
||||
// - decide what message to sign
|
||||
// - take one (unused) commitment per signing participant
|
||||
let mut signature_shares = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
// In practice, the SigningPackage must be sent to all participants
|
||||
// involved in the current signing (at least min_signers participants),
|
||||
// using an authenticate channel (and confidential if the message is secret).
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_identifier in nonces.keys() {
|
||||
let key_package = &key_packages[participant_identifier];
|
||||
|
||||
let nonces_to_use = &nonces[participant_identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
|
||||
|
||||
// In practice, the signature share must be sent to the Coordinator
|
||||
// using an authenticated channel.
|
||||
signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate (also verifies the signature shares)
|
||||
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key).
|
||||
assert!(pubkeys
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
|
||||
# Ok::<(), frost::Error>(())
|
||||
```
|
||||
|
|
|
@ -1,32 +1,98 @@
|
|||
An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers
|
||||
of signers (FROST).
|
||||
|
||||
## Examples
|
||||
## Example: key generation with trusted dealer and FROST signing
|
||||
|
||||
Creating a key with a trusted dealer and splitting into shares; then signing a message
|
||||
and aggregating the signature. Note that the example just simulates a distributed
|
||||
scenario in a single thread and it abstracts away any communication between peers.
|
||||
|
||||
Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the
|
||||
signature:
|
||||
|
||||
```rust
|
||||
use frost_ristretto255 as frost;
|
||||
use rand::thread_rng;
|
||||
use frost_ristretto255::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let msg = b"Hello!";
|
||||
let mut rng = thread_rng();
|
||||
let max_signers = 5;
|
||||
let min_signers = 3;
|
||||
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
|
||||
|
||||
// Generate a secret key and sign the message
|
||||
let sk = SigningKey::new(thread_rng());
|
||||
let sig = sk.sign(thread_rng(), msg);
|
||||
// Verifies the secret shares from the dealer and store them in a HashMap.
|
||||
// In practice, the KeyPackages must be sent to its respective participants
|
||||
// through a confidential and authenticated channel.
|
||||
let key_packages: HashMap<_, _> = shares
|
||||
.into_iter()
|
||||
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes`
|
||||
let sig_bytes = sig.to_bytes();
|
||||
let pk_bytes = VerifyingKey::from(&sk).to_bytes();
|
||||
let mut nonces = HashMap::new();
|
||||
let mut commitments = HashMap::new();
|
||||
|
||||
// Deserialize and verify the signature.
|
||||
let sig = Signature::from_bytes(sig_bytes)?;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
assert!(
|
||||
VerifyingKey::from_bytes(pk_bytes)
|
||||
.and_then(|pk| pk.verify(msg, &sig))
|
||||
.is_ok()
|
||||
);
|
||||
# Ok::<(), Error>(())
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_index in 1..(min_signers as u16 + 1) {
|
||||
let participant_identifier = participant_index.try_into().expect("should be nonzero");
|
||||
// Generate one (1) nonce and one SigningCommitments instance for each
|
||||
// participant, up to _threshold_.
|
||||
let (nonce, commitment) = frost::round1::commit(
|
||||
participant_identifier,
|
||||
key_packages[&participant_identifier].secret_share(),
|
||||
&mut rng,
|
||||
);
|
||||
// In practice, the nonces and commitment must be sent to the coordinator
|
||||
// (or to every other participant if there is no coordinator) using
|
||||
// an authenticated channel.
|
||||
nonces.insert(participant_identifier, nonce);
|
||||
commitments.insert(participant_identifier, commitment);
|
||||
}
|
||||
|
||||
// This is what the signature aggregator / coordinator needs to do:
|
||||
// - decide what message to sign
|
||||
// - take one (unused) commitment per signing participant
|
||||
let mut signature_shares = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
// In practice, the SigningPackage must be sent to all participants
|
||||
// involved in the current signing (at least min_signers participants),
|
||||
// using an authenticate channel (and confidential if the message is secret).
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// In practice, each iteration of this loop will be executed by its respective participant.
|
||||
for participant_identifier in nonces.keys() {
|
||||
let key_package = &key_packages[participant_identifier];
|
||||
|
||||
let nonces_to_use = &nonces[participant_identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
|
||||
|
||||
// In practice, the signature share must be sent to the Coordinator
|
||||
// using an authenticated channel.
|
||||
signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate (also verifies the signature shares)
|
||||
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key).
|
||||
assert!(pubkeys
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
|
||||
# Ok::<(), frost::Error>(())
|
||||
```
|
||||
|
|
|
@ -108,6 +108,23 @@ fn write_docs(
|
|||
fs::write(filename, code).unwrap();
|
||||
}
|
||||
|
||||
/// Copy a file into a new one, replacing the strings in `original_strings`
|
||||
/// by the respective one in `replacement_strings` in the process.
|
||||
fn copy_and_replace(
|
||||
origin_filename: &str,
|
||||
destination_filename: &str,
|
||||
original_strings: &[&str],
|
||||
replacement_strings: &[&str],
|
||||
) {
|
||||
let mut text = fs::read_to_string(origin_filename).unwrap();
|
||||
|
||||
for (from, to) in std::iter::zip(original_strings, replacement_strings) {
|
||||
text = text.replace(from, to)
|
||||
}
|
||||
|
||||
fs::write(destination_filename, text).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let docs = read_docs(
|
||||
"frost-ristretto255/src/lib.rs",
|
||||
|
@ -115,6 +132,9 @@ fn main() {
|
|||
);
|
||||
let old_suite_names_doc = &["FROST(ristretto255, SHA-512)"];
|
||||
|
||||
let readme_filename = "frost-ristretto255/README.md";
|
||||
let original_strings = &["frost_ristretto255", "Ristretto group"];
|
||||
|
||||
// To add a new ciphersuite, just copy this call and replace the required strings.
|
||||
|
||||
write_docs(
|
||||
|
@ -124,6 +144,12 @@ fn main() {
|
|||
old_suite_names_doc,
|
||||
&["FROST(P-256, SHA-256)"],
|
||||
);
|
||||
copy_and_replace(
|
||||
readme_filename,
|
||||
"frost-p256/README.md",
|
||||
original_strings,
|
||||
&["frost_p256", "P-256 curve"],
|
||||
);
|
||||
|
||||
write_docs(
|
||||
&docs,
|
||||
|
@ -132,4 +158,10 @@ fn main() {
|
|||
old_suite_names_doc,
|
||||
&["FROST(Ed25519, SHA-512)"],
|
||||
);
|
||||
copy_and_replace(
|
||||
readme_filename,
|
||||
"frost-ed25519/README.md",
|
||||
original_strings,
|
||||
&["frost_ed25519", "Ed25519 curve"],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue