package peergos.shared.user;
import peergos.shared.*;
import peergos.shared.cbor.*;
import peergos.shared.corenode.*;
import peergos.shared.crypto.*;
import peergos.shared.crypto.asymmetric.*;
import peergos.shared.crypto.symmetric.*;
import peergos.shared.io.ipfs.multihash.*;
import peergos.shared.merklebtree.*;
import peergos.shared.mutable.*;
import peergos.shared.storage.*;
import peergos.shared.user.fs.*;
import peergos.shared.util.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;
public class WriterData implements Cborable {
/**
* Represents the merkle node that a public key maps to
*/
// publicly readable and present on owner keys
public final Optional<UserGenerationAlgorithm> generationAlgorithm;
// accessible under IPFS address $hash/public
public final Optional<FilePointer> publicData;
// The public key to encrypt follow requests to, accessible under IPFS address $hash/inbound
public final Optional<PublicBoxingKey> followRequestReceiver;
// accessible under IPFS address $hash/owned
public final Set<PublicSigningKey> ownedKeys;
// Encrypted
// accessible under IPFS address $hash/static (present on owner keys)
public final Optional<UserStaticData> staticData;
// accessible under IPFS address $hash/btree (present on writer keys)
public final Optional<Multihash> btree;
/**
*
* @param generationAlgorithm The algorithm used to create the users key pairs and root key from the username and password
* @param publicData A readable pointer to a subtree made public by this key
* @param ownedKeys Any public keys owned by this key
* @param staticData Any static data owner by this key (list of entry points)
* @param btree Any file tree owned by this key
*/
public WriterData(Optional<UserGenerationAlgorithm> generationAlgorithm,
Optional<FilePointer> publicData,
Optional<PublicBoxingKey> followRequestReceiver,
Set<PublicSigningKey> ownedKeys,
Optional<UserStaticData> staticData,
Optional<Multihash> btree) {
this.generationAlgorithm = generationAlgorithm;
this.publicData = publicData;
this.followRequestReceiver = followRequestReceiver;
this.ownedKeys = ownedKeys;
this.staticData = staticData;
this.btree = btree;
}
public WriterData withBtree(Multihash treeRoot) {
return new WriterData(generationAlgorithm, publicData, followRequestReceiver, ownedKeys, staticData, Optional.of(treeRoot));
}
public WriterData withOwnedKeys(Set<PublicSigningKey> owned) {
return new WriterData(generationAlgorithm, publicData, followRequestReceiver, owned, staticData, btree);
}
public static WriterData buildSubtree(Multihash btreeRoot) {
return new WriterData(Optional.empty(), Optional.empty(), Optional.empty(), Collections.emptySet(), Optional.empty(), Optional.of(btreeRoot));
}
public static WriterData createEmpty() {
return new WriterData(Optional.empty(),
Optional.empty(),
Optional.empty(),
Collections.emptySet(),
Optional.empty(),
Optional.empty());
}
public static WriterData createEmpty(Optional<PublicBoxingKey> followRequestReceiver, SymmetricKey rootKey) {
return new WriterData(Optional.of(UserGenerationAlgorithm.getDefault()),
Optional.empty(),
followRequestReceiver,
Collections.emptySet(),
Optional.of(new UserStaticData(rootKey)),
Optional.empty());
}
public CommittedWriterData committed(MaybeMultihash hash) {
return new CommittedWriterData(hash, this);
}
public CompletableFuture<CommittedWriterData> removeFromStaticData(FileTreeNode fileTreeNode, SigningKeyPair signer,
MaybeMultihash currentHash,
NetworkAccess network, Consumer<CommittedWriterData> updater) {
FilePointer pointer = fileTreeNode.getPointer().filePointer;
return staticData.map(sd -> {
boolean isRemoved = sd.remove(pointer);
if (isRemoved)
return commit(signer, currentHash, network, updater);
CommittedWriterData committed = committed(currentHash);
updater.accept(committed);
return CompletableFuture.completedFuture(committed);
}).orElse(CompletableFuture.completedFuture(committed(currentHash)));
}
public CompletableFuture<CommittedWriterData> changeKeys(SigningKeyPair signer, MaybeMultihash currentHash,
PublicBoxingKey followRequestReceiver, SymmetricKey newKey,
NetworkAccess network, Consumer<CommittedWriterData> updater) {
Optional<UserStaticData> newEntryPoints = staticData.map(sd -> sd.withKey(newKey));
WriterData updated = new WriterData(generationAlgorithm, publicData, Optional.of(followRequestReceiver), ownedKeys, newEntryPoints, btree);
return updated.commit(signer, MaybeMultihash.EMPTY(), network, updater);
}
public CompletableFuture<CommittedWriterData> commit(SigningKeyPair signer, MaybeMultihash currentHash,
NetworkAccess network, Consumer<CommittedWriterData> updater) {
return commit(signer, currentHash, network.mutable, network.dhtClient, updater);
}
public CompletableFuture<CommittedWriterData> commit(SigningKeyPair signer, MaybeMultihash currentHash,
MutablePointers mutable, ContentAddressedStorage immutable,
Consumer<CommittedWriterData> updater) {
byte[] raw = serialize();
return immutable.put(signer.publicSigningKey, raw)
.thenCompose(blobHash -> {
MaybeMultihash newHash = MaybeMultihash.of(blobHash);
if (newHash.equals(currentHash)) {
// nothing has changed
CommittedWriterData committed = committed(newHash);
updater.accept(committed);
return CompletableFuture.completedFuture(committed);
}
HashCasPair cas = new HashCasPair(currentHash, newHash);
byte[] signed = signer.signMessage(cas.serialize());
return mutable.setPointer(signer.publicSigningKey, signer.publicSigningKey, signed)
.thenApply(res -> {
if (!res)
throw new IllegalStateException("Corenode Crypto CAS failed!");
CommittedWriterData committed = committed(newHash);
updater.accept(committed);
return committed;
});
});
}
@Override
public CborObject toCbor() {
Map<String, CborObject> result = new TreeMap<>();
generationAlgorithm.ifPresent(alg -> result.put("algorithm", alg.toCbor()));
publicData.ifPresent(rfp -> result.put("public", rfp.toCbor()));
followRequestReceiver.ifPresent(boxer -> result.put("inbound", boxer.toCbor()));
List<CborObject> ownedKeyStrings = ownedKeys.stream().map(Cborable::toCbor).collect(Collectors.toList());
result.put("owned", new CborObject.CborList(ownedKeyStrings));
staticData.ifPresent(sd -> result.put("static", sd.toCbor()));
btree.ifPresent(btree -> result.put("btree", new CborObject.CborMerkleLink(btree)));
return CborObject.CborMap.build(result);
}
public static Optional<UserGenerationAlgorithm> extractUserGenerationAlgorithm(CborObject cbor) {
CborObject.CborMap map = (CborObject.CborMap) cbor;
Function<String, Optional<CborObject>> extract = key -> {
CborObject.CborString cborKey = new CborObject.CborString(key);
return map.values.containsKey(cborKey) ? Optional.of(map.values.get(cborKey)) : Optional.empty();
};
return extract.apply("algorithm").map(UserGenerationAlgorithm::fromCbor);
}
public static WriterData fromCbor(CborObject cbor, SymmetricKey rootKey) {
if (! ( cbor instanceof CborObject.CborMap))
throw new IllegalStateException("Cbor for WriterData should be a map! " + cbor);
CborObject.CborMap map = (CborObject.CborMap) cbor;
Function<String, Optional<CborObject>> extract = key -> {
CborObject.CborString cborKey = new CborObject.CborString(key);
return map.values.containsKey(cborKey) ? Optional.of(map.values.get(cborKey)) : Optional.empty();
};
Optional<UserGenerationAlgorithm> algo = extractUserGenerationAlgorithm(cbor);
Optional<FilePointer> publicData = extract.apply("public").map(FilePointer::fromCbor);
Optional<PublicBoxingKey> followRequestReceiver = extract.apply("inbound").map(raw -> PublicBoxingKey.fromCbor(raw));
CborObject.CborList ownedList = (CborObject.CborList) map.values.get(new CborObject.CborString("owned"));
Set<PublicSigningKey> owned = ownedList.value.stream().map(PublicSigningKey::fromCbor).collect(Collectors.toSet());
// rootKey is null for other people parsing our WriterData who don't have our root key
Optional<UserStaticData> staticData = rootKey == null ? Optional.empty() : extract.apply("static").map(raw -> UserStaticData.fromCbor(raw, rootKey));
Optional<Multihash> btree = extract.apply("btree").map(val -> ((CborObject.CborMerkleLink)val).target);
return new WriterData(algo, publicData, followRequestReceiver, owned, staticData, btree);
}
}