Closed ehsan6sha closed 2 years ago
Thanks for filing this @ehsan6sha :pray: The proposed solution sounds great. If you want this to happen sooner, please feel free to open a PR that makes this change, perhaps additionally adds a doc comment for the constructor (if not present already), and I'd be happy to have that merged soon! :)
Actually there's already PrivateRef::from_revision_key
. It's public, but not documented, and I think it may just not have been released yet.
I think what you want to do is deterministically derive the root PrivateRef
from a shared secret on two devices.
You'd both have to deterministically generate the inumber
and Ratchet
for the root PrivateDirectory
.
It'd work like this:
fn derive_private_ref(shared_secret: [u8; 32]) -> PrivateRef {
// blake3 or some other keyed hashing function
let inumber = blake3::keyed_hash(&shared_secret, b"derived inumber");
let ratchet_seed = blake3::keyed_hash(&shared_secret, b"derived ratchet seed");
let ratchet = Ratchet::zero(ratchet_seed);
let revision_key = RevisionKey::from(&ratchet);
let mut namefilter = Namefilter::default();
namefilter.add(&inumber);
namefilter.add(&revision_key.0.as_bytes());
namefilter.saturate();
let saturated_name_hash = Sha3_256::hash(&namefilter);
PrivateRef::from_revision_key(saturated_name_hash, revision_key)
}
The remaining problem is that this requires the original PrivateDirectory
to have been generated with the same inumber
and revision_key
. That's not possible today unless we expose some operators that allow this. We could work on that!
Actually there's already
PrivateRef::from_revision_key
. It's public, but not documented, and I think it may just not have been released yet.I think what you want to do is deterministically derive the root
PrivateRef
from a shared secret on two devices.You'd both have to deterministically generate the
inumber
andRatchet
for the rootPrivateDirectory
.It'd work like this:
fn derive_private_ref(shared_secret: [u8; 32]) -> PrivateRef { // blake3 or some other keyed hashing function let inumber = blake3::keyed_hash(&shared_secret, b"derived inumber"); let ratchet_seed = blake3::keyed_hash(&shared_secret, b"derived ratchet seed"); let ratchet = Ratchet::zero(ratchet_seed); let revision_key = RevisionKey::from(&ratchet); let mut namefilter = Namefilter::default(); namefilter.add(&inumber); namefilter.add(&revision_key.0.as_bytes()); namefilter.saturate(); let saturated_name_hash = Sha3_256::hash(&namefilter); PrivateRef::from_revision_key(saturated_name_hash, revision_key) }
The remaining problem is that this requires the original
PrivateDirectory
to have been generated with the sameinumber
andrevision_key
. That's not possible today unless we expose some operators that allow this. We could work on that!
So basically we have a public function available to create the PrivateRef from an external key here and now we need to do modifications to feed this PrivateRef to the PrivateDirectory? I think needed changes would be in the methods below, right? https://github.com/wnfs-wg/rs-wnfs/blob/80dbe82dd41e9eebda77960b930458d4d1feeb69/wnfs/src/private/node.rs#L500
So basically we have a public function available to create the PrivateRef from an external key here and now we need to do modifications to feed this PrivateRef to the PrivateDirectory? I think needed changes would be in the methods below, right?
Not quite.
PrivateDirectory
that fits a given PrivateRef
. You need to create both from a common seed
.@matheus23 If the private key can be hashed, we can expose the seed part of the ratchet generation to make it available for deterministic key gen cases. I believe we wanted to do this at one point but it didn't make it in after some refactor.
impl PrivateNodeHeader {
pub(crate) fn with_seed<R: RngCore>(parent_bare_name: Namefilter, ratchet_seed: HashOutput, rng: &mut R) -> Self {
let inumber = utils::get_random_bytes::<HASH_BYTE_SIZE>(rng);
Self {
bare_name: {
let mut namefilter = parent_bare_name;
namefilter.add(&inumber);
namefilter
},
ratchet: Ratchet::zero(ratchet_seed),
inumber,
}
}
// ...
}
impl PrivateDirectory {
pub fn with_seed<R: RngCore>(parent_bare_name: Namefilter, time: DateTime<Utc>, ratchet_seed: HashOutput, rng: &mut R) -> Self {
Self {
version: Version::new(0, 2, 0),
header: PrivateNodeHeader::with_seed(parent_bare_name, ratchet_seed, rng),
metadata: Metadata::new(time),
entries: BTreeMap::new(),
}
}
// ...
}
Yeah this is practically almost what we want I think. The only thing missing is that the inumber
shouldn't be random.
Technically you could achieve that by just passing in the same deterministic impl RngCore
to both methods, but at this point it may be easier to just pass in the inumber
directly.
Yeah passing in a deterministic Rng would work too but it is more work than necessary and can easily break with future changes depending on how we use rng. I also considered exposing the inumber but a user of the interface might not pay too much attention to its randomness. Maybe a warning in the doc that screams the importance of it being crypto secure to avoid collision.
NB: Feature requests will only be considered if they solve a pain
Summary: Exposing a constructor for PrivateRef
Problem
The only way to obtain the root private key of WNFS-RS and use it on antoher device(login), right now, is to call .get_private_ref() on the directory on the first device and then serialize it and move it over to the other device. This doesn't allow deriving it deterministically. In both the current version of fs used in webnative and WNFS go there is a constructor interface available to the programmer that can feed the private key that WNFS uses for the root. However, it is not exposed in WNFS rust version. exposing it allows application developer to feed the private key from their own login process (whatever it is) and it is deterministic, and based on users' login and not random.
Impact
We can then decrypt WNFS tree on any device that the user has without needing to transfer the keys as long as users logs in on different devices and the program can create the same keypair. It gives more flexibility to the application developer on what approach they want and also gives the user more control over which private key to use. If developer wants to feed a deterministic key, they can, if they want a random key, they still can do it.
Solution
Exposing the constructor for PrivateRef
Detail
Is your feature request related to a problem? Please describe. It's frustrating where the login cannot be linked to WNFS encryption and we still need to transfer the private key between multiple devices even when the user completes a login process.
Describe the solution you'd like Like webnative js version and go version, I want to be able to construct the Private_ref myself from a keypair or seed hash (both work).
Describe alternatives you've considered The only way to obtain the root private key of WNFS-RS and use it on antoher device(login), right now, is to call .get_private_ref() on the directory on the first device and then serialize it and move it over to the other device. This doesn't allow deriving it deterministically.
Additional context It is also discussed on IPFS Discord on #wnfs with Matheus and Boris