huskies: merge 977
This commit is contained in:
Generated
+16
-701
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -18,7 +18,7 @@ poem = { version = "3", features = ["websocket", "test"] }
|
|||||||
portable-pty = "0.9.0"
|
portable-pty = "0.9.0"
|
||||||
reqwest = { version = "0.13.3", features = ["json", "stream"] }
|
reqwest = { version = "0.13.3", features = ["json", "stream"] }
|
||||||
rust-embed = "8"
|
rust-embed = "8"
|
||||||
fastcrypto = "0.1.9"
|
ed25519-dalek = { version = "2", default-features = false, features = ["rand_core"] }
|
||||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ bft = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bft-crdt-derive = { path = "bft-crdt-derive" }
|
bft-crdt-derive = { path = "bft-crdt-derive" }
|
||||||
colored = "3"
|
colored = "3"
|
||||||
fastcrypto = { workspace = true }
|
ed25519-dalek = { workspace = true }
|
||||||
indexmap = { workspace = true, features = ["serde"] }
|
indexmap = { workspace = true, features = ["serde"] }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
random_color = "1"
|
random_color = "1"
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
use crate::keypair::Ed25519KeyPair;
|
||||||
use fastcrypto::traits::KeyPair;
|
|
||||||
|
|
||||||
use crate::debug::DebugView;
|
use crate::debug::DebugView;
|
||||||
use crate::keypair::SignedDigest;
|
use crate::keypair::SignedDigest;
|
||||||
@@ -36,7 +35,7 @@ impl<T: CrdtNode + DebugView> BaseCrdt<T> {
|
|||||||
/// routing messages to the right BaseCRDT. Usually you should just make a single
|
/// routing messages to the right BaseCRDT. Usually you should just make a single
|
||||||
/// struct that contains all the state you need.
|
/// struct that contains all the state you need.
|
||||||
pub fn new(keypair: &Ed25519KeyPair) -> Self {
|
pub fn new(keypair: &Ed25519KeyPair) -> Self {
|
||||||
let id = keypair.public().0.to_bytes();
|
let id = keypair.verifying_key().to_bytes();
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
doc: T::new(id, vec![]),
|
doc: T::new(id, vec![]),
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
//! [`SignedOp`], [`OpState`], and the causal queue capacity constant.
|
//! [`SignedOp`], [`OpState`], and the causal queue capacity constant.
|
||||||
|
|
||||||
use fastcrypto::traits::VerifyingKey;
|
use crate::keypair::{Ed25519KeyPair, Ed25519PublicKey, Ed25519Signature};
|
||||||
use fastcrypto::{
|
use ed25519_dalek::Verifier as _;
|
||||||
ed25519::{Ed25519KeyPair, Ed25519PublicKey, Ed25519Signature},
|
|
||||||
traits::{KeyPair, ToFromBytes},
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{serde_as, Bytes};
|
use serde_with::{serde_as, Bytes};
|
||||||
|
|
||||||
@@ -107,16 +104,15 @@ impl SignedOp {
|
|||||||
/// Sign this digest with the given keypair. Shouldn't need to be called manually,
|
/// Sign this digest with the given keypair. Shouldn't need to be called manually,
|
||||||
/// just use [`SignedOp::from_op`] instead
|
/// just use [`SignedOp::from_op`] instead
|
||||||
fn sign_digest(&mut self, keypair: &Ed25519KeyPair) {
|
fn sign_digest(&mut self, keypair: &Ed25519KeyPair) {
|
||||||
self.signed_digest = sign(keypair, &self.digest()).sig.to_bytes()
|
self.signed_digest = sign(keypair, &self.digest()).to_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure digest was actually signed by the author it claims to be signed by
|
/// Ensure digest was actually signed by the author it claims to be signed by
|
||||||
pub fn is_valid_digest(&self) -> bool {
|
pub fn is_valid_digest(&self) -> bool {
|
||||||
let digest = Ed25519Signature::from_bytes(&self.signed_digest);
|
let digest = Ed25519Signature::from_bytes(&self.signed_digest);
|
||||||
let pubkey = Ed25519PublicKey::from_bytes(&self.author());
|
match Ed25519PublicKey::from_bytes(&self.author()) {
|
||||||
match (digest, pubkey) {
|
Ok(pubkey) => pubkey.verify(&self.digest(), &digest).is_ok(),
|
||||||
(Ok(digest), Ok(pubkey)) => pubkey.verify(&self.digest(), &digest).is_ok(),
|
Err(_) => false,
|
||||||
(_, _) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +122,7 @@ impl SignedOp {
|
|||||||
keypair: &Ed25519KeyPair,
|
keypair: &Ed25519KeyPair,
|
||||||
depends_on: Vec<SignedDigest>,
|
depends_on: Vec<SignedDigest>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let author = keypair.public().0.to_bytes();
|
let author = keypair.verifying_key().to_bytes();
|
||||||
let mut new = Self {
|
let mut new = Self {
|
||||||
inner: Op {
|
inner: Op {
|
||||||
content: value.content.map(|c| c.view()),
|
content: value.content.map(|c| c.view()),
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
//! Ed25519 keypair utilities and type aliases for node identity and signing.
|
//! Ed25519 keypair utilities and type aliases for node identity and signing.
|
||||||
//!
|
//!
|
||||||
//! Provides the [`AuthorId`] and [`SignedDigest`] type aliases, a SHA-256 helper,
|
//! Provides the [`AuthorId`] and [`SignedDigest`] type aliases, a SHA-256 helper,
|
||||||
//! and convenience wrappers around the `fastcrypto` Ed25519 primitives used
|
//! and convenience wrappers around the `ed25519-dalek` Ed25519 primitives used
|
||||||
//! throughout the CRDT codebase.
|
//! throughout the CRDT codebase.
|
||||||
|
|
||||||
use fastcrypto::traits::VerifyingKey;
|
use ed25519_dalek::Signer as _;
|
||||||
pub use fastcrypto::{
|
use ed25519_dalek::Verifier as _;
|
||||||
ed25519::{
|
|
||||||
Ed25519KeyPair, Ed25519PublicKey, Ed25519Signature, ED25519_PUBLIC_KEY_LENGTH,
|
|
||||||
ED25519_SIGNATURE_LENGTH,
|
|
||||||
},
|
|
||||||
traits::{KeyPair, Signer, ToFromBytes},
|
|
||||||
// Verifier,
|
|
||||||
};
|
|
||||||
use rand::RngCore as _;
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
/// Ed25519 signing key (private + public pair).
|
||||||
|
pub type Ed25519KeyPair = ed25519_dalek::SigningKey;
|
||||||
|
/// Ed25519 verifying (public) key.
|
||||||
|
pub type Ed25519PublicKey = ed25519_dalek::VerifyingKey;
|
||||||
|
/// Ed25519 signature.
|
||||||
|
pub type Ed25519Signature = ed25519_dalek::Signature;
|
||||||
|
|
||||||
|
/// Length of an Ed25519 public key in bytes.
|
||||||
|
pub const ED25519_PUBLIC_KEY_LENGTH: usize = 32;
|
||||||
|
/// Length of an Ed25519 signature in bytes.
|
||||||
|
pub const ED25519_SIGNATURE_LENGTH: usize = 64;
|
||||||
|
|
||||||
/// Represents the ID of a unique node. An Ed25519 public key
|
/// Represents the ID of a unique node. An Ed25519 public key
|
||||||
pub type AuthorId = [u8; ED25519_PUBLIC_KEY_LENGTH];
|
pub type AuthorId = [u8; ED25519_PUBLIC_KEY_LENGTH];
|
||||||
|
|
||||||
@@ -49,9 +53,10 @@ pub fn sha256(input: String) -> [u8; 32] {
|
|||||||
|
|
||||||
/// Generate a random Ed25519 keypair from OS rng
|
/// Generate a random Ed25519 keypair from OS rng
|
||||||
pub fn make_keypair() -> Ed25519KeyPair {
|
pub fn make_keypair() -> Ed25519KeyPair {
|
||||||
|
use rand::RngCore as _;
|
||||||
let mut seed = [0u8; 32];
|
let mut seed = [0u8; 32];
|
||||||
rand::rng().fill_bytes(&mut seed);
|
rand::rng().fill_bytes(&mut seed);
|
||||||
Ed25519KeyPair::from_bytes(&seed).expect("32-byte seed always yields a valid Ed25519 keypair")
|
Ed25519KeyPair::from_bytes(&seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a byte array
|
/// Sign a byte array
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
use crate::debug::{debug_path_mismatch, debug_type_mismatch};
|
use crate::debug::{debug_path_mismatch, debug_type_mismatch};
|
||||||
use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, JsonValue, SignedOp};
|
use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, JsonValue, SignedOp};
|
||||||
use crate::keypair::{sha256, AuthorId};
|
use crate::keypair::{sha256, AuthorId, Ed25519KeyPair};
|
||||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -45,8 +45,7 @@ sqlx = { workspace = true }
|
|||||||
wait-timeout = "0.2.1"
|
wait-timeout = "0.2.1"
|
||||||
bft-json-crdt = { path = "../crates/bft-json-crdt", default-features = false, features = ["bft"] }
|
bft-json-crdt = { path = "../crates/bft-json-crdt", default-features = false, features = ["bft"] }
|
||||||
source-map-gen = { path = "../crates/source-map-gen" }
|
source-map-gen = { path = "../crates/source-map-gen" }
|
||||||
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
ed25519-dalek = { workspace = true }
|
||||||
fastcrypto = { workspace = true }
|
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
indexmap = { workspace = true, features = ["serde"] }
|
indexmap = { workspace = true, features = ["serde"] }
|
||||||
|
|
||||||
|
|||||||
@@ -198,9 +198,8 @@ mod tests {
|
|||||||
use super::super::write::write_item_str;
|
use super::super::write::write_item_str;
|
||||||
use super::*;
|
use super::*;
|
||||||
use bft_json_crdt::json_crdt::OpState;
|
use bft_json_crdt::json_crdt::OpState;
|
||||||
use bft_json_crdt::keypair::make_keypair;
|
use bft_json_crdt::keypair::{Ed25519KeyPair, make_keypair};
|
||||||
use bft_json_crdt::op::ROOT_ID;
|
use bft_json_crdt::op::ROOT_ID;
|
||||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -438,8 +437,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_sync_multi_author() {
|
fn delta_sync_multi_author() {
|
||||||
use fastcrypto::traits::KeyPair;
|
|
||||||
|
|
||||||
let kp_a = make_keypair();
|
let kp_a = make_keypair();
|
||||||
let kp_b = make_keypair();
|
let kp_b = make_keypair();
|
||||||
let mut crdt_a = BaseCrdt::<PipelineDoc>::new(&kp_a);
|
let mut crdt_a = BaseCrdt::<PipelineDoc>::new(&kp_a);
|
||||||
@@ -454,7 +451,7 @@ mod tests {
|
|||||||
|
|
||||||
// Peer has seen all of A's ops but none of B's.
|
// Peer has seen all of A's ops but none of B's.
|
||||||
let mut peer_clock = VectorClock::new();
|
let mut peer_clock = VectorClock::new();
|
||||||
let author_a_hex = hex::encode(&kp_a.public().0.to_bytes());
|
let author_a_hex = hex::encode(&kp_a.verifying_key().to_bytes());
|
||||||
peer_clock.insert(author_a_hex, 30);
|
peer_clock.insert(author_a_hex, 30);
|
||||||
|
|
||||||
let delta = local_ops_since(&all_ops, &peer_clock);
|
let delta = local_ops_since(&all_ops, &peer_clock);
|
||||||
@@ -468,14 +465,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_vector_clock_from_ops() {
|
fn build_vector_clock_from_ops() {
|
||||||
use fastcrypto::traits::KeyPair;
|
|
||||||
|
|
||||||
let kp = make_keypair();
|
let kp = make_keypair();
|
||||||
let mut crdt = BaseCrdt::<PipelineDoc>::new(&kp);
|
let mut crdt = BaseCrdt::<PipelineDoc>::new(&kp);
|
||||||
let ops = make_ops(&kp, &mut crdt, 10, "631_vc");
|
let ops = make_ops(&kp, &mut crdt, 10, "631_vc");
|
||||||
|
|
||||||
let clock = build_clock(&ops);
|
let clock = build_clock(&ops);
|
||||||
let author_hex = hex::encode(&kp.public().0.to_bytes());
|
let author_hex = hex::encode(&kp.verifying_key().to_bytes());
|
||||||
|
|
||||||
assert_eq!(clock.len(), 1, "single author should produce 1 clock entry");
|
assert_eq!(clock.len(), 1, "single author should produce 1 clock entry");
|
||||||
assert_eq!(clock[&author_hex], 10, "clock should show 10 ops");
|
assert_eq!(clock[&author_hex], 10, "clock should show 10 ops");
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use super::read::read_item;
|
|||||||
use bft_json_crdt::json_crdt::*;
|
use bft_json_crdt::json_crdt::*;
|
||||||
use bft_json_crdt::lww_crdt::LwwRegisterCrdt;
|
use bft_json_crdt::lww_crdt::LwwRegisterCrdt;
|
||||||
use bft_json_crdt::op::ROOT_ID;
|
use bft_json_crdt::op::ROOT_ID;
|
||||||
use fastcrypto::traits::{Signer, ToFromBytes};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use super::state::{apply_and_persist, get_crdt, rebuild_node_index};
|
use super::state::{apply_and_persist, get_crdt, rebuild_node_index};
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ use std::path::Path;
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use bft_json_crdt::json_crdt::{BaseCrdt, CrdtNode, JsonValue, SignedOp};
|
use bft_json_crdt::json_crdt::{BaseCrdt, CrdtNode, JsonValue, SignedOp};
|
||||||
use bft_json_crdt::keypair::make_keypair;
|
use bft_json_crdt::keypair::{Ed25519KeyPair, make_keypair};
|
||||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
|
||||||
use fastcrypto::traits::ToFromBytes;
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use sqlx::sqlite::SqliteConnectOptions;
|
use sqlx::sqlite::SqliteConnectOptions;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
@@ -192,14 +190,14 @@ async fn load_or_create_keypair(pool: &SqlitePool) -> Result<Ed25519KeyPair, sql
|
|||||||
|
|
||||||
if let Some((seed,)) = row {
|
if let Some((seed,)) = row {
|
||||||
// Reconstruct from stored seed. The seed is the 32-byte private key.
|
// Reconstruct from stored seed. The seed is the 32-byte private key.
|
||||||
if let Ok(kp) = Ed25519KeyPair::from_bytes(&seed) {
|
if let Ok(arr) = TryInto::<[u8; 32]>::try_into(seed) {
|
||||||
return Ok(kp);
|
return Ok(Ed25519KeyPair::from_bytes(&arr));
|
||||||
}
|
}
|
||||||
slog!("[crdt] Stored keypair invalid, regenerating");
|
slog!("[crdt] Stored keypair invalid, regenerating");
|
||||||
}
|
}
|
||||||
|
|
||||||
let kp = make_keypair();
|
let kp = make_keypair();
|
||||||
let seed = kp.as_bytes().to_vec();
|
let seed = kp.to_bytes().to_vec();
|
||||||
sqlx::query("INSERT INTO crdt_node_identity (id, seed) VALUES (1, ?1) ON CONFLICT(id) DO UPDATE SET seed = excluded.seed")
|
sqlx::query("INSERT INTO crdt_node_identity (id, seed) VALUES (1, ?1) ON CONFLICT(id) DO UPDATE SET seed = excluded.seed")
|
||||||
.bind(&seed)
|
.bind(&seed)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp};
|
use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp};
|
||||||
use bft_json_crdt::keypair::make_keypair;
|
use bft_json_crdt::keypair::{Ed25519KeyPair, make_keypair};
|
||||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
|
||||||
use super::VectorClock;
|
use super::VectorClock;
|
||||||
|
|||||||
@@ -604,7 +604,6 @@ fn v2_delta_sync_via_clock_exchange() {
|
|||||||
use bft_json_crdt::json_crdt::BaseCrdt;
|
use bft_json_crdt::json_crdt::BaseCrdt;
|
||||||
use bft_json_crdt::keypair::make_keypair;
|
use bft_json_crdt::keypair::make_keypair;
|
||||||
use bft_json_crdt::op::ROOT_ID;
|
use bft_json_crdt::op::ROOT_ID;
|
||||||
use fastcrypto::traits::KeyPair;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::crdt_state::PipelineDoc;
|
use crate::crdt_state::PipelineDoc;
|
||||||
@@ -641,7 +640,7 @@ fn v2_delta_sync_via_clock_exchange() {
|
|||||||
assert_eq!(crdt_b.doc.items.view().len(), 3);
|
assert_eq!(crdt_b.doc.items.view().len(), 3);
|
||||||
|
|
||||||
// Build B's clock.
|
// Build B's clock.
|
||||||
let author_a_hex = crate::crdt_state::hex::encode(&kp_a.public().0.to_bytes());
|
let author_a_hex = crate::crdt_state::hex::encode(&kp_a.verifying_key().to_bytes());
|
||||||
let mut clock_b = std::collections::HashMap::new();
|
let mut clock_b = std::collections::HashMap::new();
|
||||||
clock_b.insert(author_a_hex.clone(), 3u64);
|
clock_b.insert(author_a_hex.clone(), 3u64);
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,6 @@
|
|||||||
//! `verify_challenge` primitive but leaves the allow-list to story 480.
|
//! `verify_challenge` primitive but leaves the allow-list to story 480.
|
||||||
|
|
||||||
use bft_json_crdt::keypair::{Ed25519KeyPair, Ed25519Signature, sign};
|
use bft_json_crdt::keypair::{Ed25519KeyPair, Ed25519Signature, sign};
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
use fastcrypto::traits::{KeyPair, ToFromBytes};
|
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
@@ -84,7 +82,7 @@ pub fn generate_challenge() -> ChallengeHex {
|
|||||||
/// encoding steps.
|
/// encoding steps.
|
||||||
pub fn sign_challenge(keypair: &Ed25519KeyPair, challenge: &str) -> SignatureHex {
|
pub fn sign_challenge(keypair: &Ed25519KeyPair, challenge: &str) -> SignatureHex {
|
||||||
let sig: Ed25519Signature = sign(keypair, challenge.as_bytes());
|
let sig: Ed25519Signature = sign(keypair, challenge.as_bytes());
|
||||||
hex_encode(sig.as_bytes())
|
hex_encode(&sig.to_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Verification ──────────────────────────────────────────────────────
|
// ── Verification ──────────────────────────────────────────────────────
|
||||||
@@ -143,7 +141,7 @@ pub fn verify_message_strict(pubkey_hex: &str, message: &[u8], signature_hex: &s
|
|||||||
/// This is the same value written to the CRDT `claimed_by` and `node_id`
|
/// This is the same value written to the CRDT `claimed_by` and `node_id`
|
||||||
/// registers, so it is the canonical node identity across all subsystems.
|
/// registers, so it is the canonical node identity across all subsystems.
|
||||||
pub fn public_key_hex(keypair: &Ed25519KeyPair) -> String {
|
pub fn public_key_hex(keypair: &Ed25519KeyPair) -> String {
|
||||||
hex_encode(keypair.public().as_bytes())
|
hex_encode(&keypair.verifying_key().to_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── File-based keypair persistence (ed25519-dalek) ────────────────────────
|
// ── File-based keypair persistence (ed25519-dalek) ────────────────────────
|
||||||
@@ -181,12 +179,12 @@ pub fn load_or_create_keypair_file(path: &std::path::Path) -> std::io::Result<No
|
|||||||
"node identity key file must contain exactly 32 bytes",
|
"node identity key file must contain exactly 32 bytes",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
SigningKey::from_bytes(&seed)
|
Ed25519KeyPair::from_bytes(&seed)
|
||||||
} else {
|
} else {
|
||||||
// Generate a fresh keypair and persist the seed.
|
// Generate a fresh keypair and persist the seed.
|
||||||
let mut seed = [0u8; 32];
|
let mut seed = [0u8; 32];
|
||||||
rand::rng().fill_bytes(&mut seed);
|
rand::rng().fill_bytes(&mut seed);
|
||||||
let sk = SigningKey::from_bytes(&seed);
|
let sk = Ed25519KeyPair::from_bytes(&seed);
|
||||||
|
|
||||||
// Create the file with mode 0600 at creation time (Unix) so the seed
|
// Create the file with mode 0600 at creation time (Unix) so the seed
|
||||||
// is never visible to other users even transiently.
|
// is never visible to other users even transiently.
|
||||||
|
|||||||
Reference in New Issue
Block a user