mirror of
https://github.com/Twigpine/node.git
synced 2026-11-09 18:08:02 -10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a46e8cbb7 | ||
|
|
e4f5e87e0b | ||
|
|
38e654a289 | ||
|
|
4f814459cc | ||
|
|
d45318281c |
No files matched your search
@@ -6,6 +6,7 @@
|
|||||||
use crate::identity::Keypair;
|
use crate::identity::Keypair;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::VerifyingKey;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
/// X25519 public key (Montgomery u) for an Ed25519 verifying key.
|
/// X25519 public key (Montgomery u) for an Ed25519 verifying key.
|
||||||
fn x25519_public(vk: &VerifyingKey) -> Result<[u8; 32]> {
|
fn x25519_public(vk: &VerifyingKey) -> Result<[u8; 32]> {
|
||||||
@@ -18,14 +19,18 @@ fn x25519_public(vk: &VerifyingKey) -> Result<[u8; 32]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// X25519 secret scalar for an Ed25519 seed (SHA-512 of seed, lower 32, clamped).
|
/// X25519 secret scalar for an Ed25519 seed (SHA-512 of seed, lower 32, clamped).
|
||||||
fn x25519_secret_from_seed(seed: &[u8; 32]) -> [u8; 32] {
|
/// Returns the scalar wrapped in `Zeroizing`, and scrubs the intermediate
|
||||||
|
/// SHA-512 digest, so no copy of this secret material lingers in freed memory.
|
||||||
|
fn x25519_secret_from_seed(seed: &[u8; 32]) -> Zeroizing<[u8; 32]> {
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
let h = Sha512::digest(seed);
|
use zeroize::Zeroize;
|
||||||
let mut s = [0u8; 32];
|
let mut h = Sha512::digest(seed);
|
||||||
|
let mut s = Zeroizing::new([0u8; 32]);
|
||||||
s.copy_from_slice(&h[..32]);
|
s.copy_from_slice(&h[..32]);
|
||||||
s[0] &= 248;
|
s[0] &= 248;
|
||||||
s[31] &= 127;
|
s[31] &= 127;
|
||||||
s[31] |= 64;
|
s[31] |= 64;
|
||||||
|
h.as_mut_slice().zeroize();
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +128,7 @@ pub fn open_blob(envelope: &[u8], keypair: &Keypair) -> Result<Vec<u8>> {
|
|||||||
.context("decode header")?;
|
.context("decode header")?;
|
||||||
let body = &envelope[p + hlen..];
|
let body = &envelope[p + hlen..];
|
||||||
|
|
||||||
let my_x = XSecret::from(x25519_secret_from_seed(&keypair.seed_bytes()));
|
let my_x = XSecret::from(*x25519_secret_from_seed(&keypair.to_seed()));
|
||||||
|
|
||||||
// Identities are blinded: no entry says which recipient it belongs to, so
|
// Identities are blinded: no entry says which recipient it belongs to, so
|
||||||
// try each one. The ChaChaBox AEAD tag authenticates, so exactly the
|
// try each one. The ChaChaBox AEAD tag authenticates, so exactly the
|
||||||
@@ -185,10 +190,10 @@ mod tests {
|
|||||||
// The X25519 public derived from the Ed25519 public must equal the
|
// The X25519 public derived from the Ed25519 public must equal the
|
||||||
// X25519 public of the X25519 secret derived from the same seed.
|
// X25519 public of the X25519 secret derived from the same seed.
|
||||||
let kp = Keypair::generate();
|
let kp = Keypair::generate();
|
||||||
let seed = kp.seed_bytes();
|
let seed = kp.to_seed();
|
||||||
let xpub_from_public = x25519_public(&kp.verifying_key()).unwrap();
|
let xpub_from_public = x25519_public(&kp.verifying_key()).unwrap();
|
||||||
let xsec = x25519_secret_from_seed(&seed);
|
let xsec = x25519_secret_from_seed(&seed);
|
||||||
let xpub_from_secret = crypto_box::SecretKey::from(xsec).public_key().to_bytes();
|
let xpub_from_secret = crypto_box::SecretKey::from(*xsec).public_key().to_bytes();
|
||||||
assert_eq!(xpub_from_public, xpub_from_secret);
|
assert_eq!(xpub_from_public, xpub_from_secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,9 @@ impl Keypair {
|
|||||||
URL_SAFE_NO_PAD.encode(sig.to_bytes())
|
URL_SAFE_NO_PAD.encode(sig.to_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The raw 32-byte Ed25519 seed. Used to derive the X25519 secret for
|
/// The raw 32-byte Ed25519 seed, wrapped in `Zeroizing` so the copy is
|
||||||
/// envelope decryption (see `crate::encrypt`).
|
/// scrubbed on drop. This is the only seed accessor; it is used to derive
|
||||||
pub fn seed_bytes(&self) -> [u8; 32] {
|
/// the X25519 secret for envelope decryption (see `crate::encrypt`).
|
||||||
self.signing_key.to_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export the signing key as raw 32-byte seed (wrapped in Zeroizing).
|
|
||||||
pub fn to_seed(&self) -> Zeroizing<[u8; 32]> {
|
pub fn to_seed(&self) -> Zeroizing<[u8; 32]> {
|
||||||
Zeroizing::new(self.signing_key.to_bytes())
|
Zeroizing::new(self.signing_key.to_bytes())
|
||||||
}
|
}
|
||||||
@@ -172,6 +168,20 @@ mod tests {
|
|||||||
assert_eq!(kp.verifying_key(), kp2.verifying_key());
|
assert_eq!(kp.verifying_key(), kp2.verifying_key());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `to_seed()` is the only seed accessor on `Keypair` (the raw, unzeroized
|
||||||
|
// `seed_bytes()` was removed in #41). Pin its return type so a bare-array
|
||||||
|
// accessor can't slip back in, and confirm it is deterministic. The
|
||||||
|
// from_seed -> verifying_key round-trip is already covered by
|
||||||
|
// `from_seed_round_trip`.
|
||||||
|
#[test]
|
||||||
|
fn to_seed_returns_zeroizing_and_is_deterministic() {
|
||||||
|
let kp = Keypair::generate();
|
||||||
|
// The type annotation is the regression guard: the seed must be handed
|
||||||
|
// out wrapped in `Zeroizing`, never as a bare `[u8; 32]`.
|
||||||
|
let seed: Zeroizing<[u8; 32]> = kp.to_seed();
|
||||||
|
assert_eq!(*kp.to_seed(), *seed); // stable across calls
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_b64_decodes_to_valid_signature() {
|
fn sign_b64_decodes_to_valid_signature() {
|
||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||||
|
|||||||
@@ -689,7 +689,7 @@ pub async fn git_receive_pack(
|
|||||||
let irys_url = state.config.irys_url.clone();
|
let irys_url = state.config.irys_url.clone();
|
||||||
let http_client = std::sync::Arc::clone(&state.http_client);
|
let http_client = std::sync::Arc::clone(&state.http_client);
|
||||||
let node_did_str = state.node_did.to_string();
|
let node_did_str = state.node_did.to_string();
|
||||||
let node_seed = state.node_keypair.seed_bytes();
|
let node_seed = state.node_keypair.to_seed();
|
||||||
let repo_name = record.name.clone();
|
let repo_name = record.name.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let pinned = crate::ipfs_pin::pin_new_objects(
|
let pinned = crate::ipfs_pin::pin_new_objects(
|
||||||
|
|||||||
Reference in new issue
Block a user