Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.cardanofoundation.signify.cesr.args.RawArgs;
import org.cardanofoundation.signify.cesr.args.RotateArgs;
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.exceptions.extraction.UnexpectedCodeException;
import org.cardanofoundation.signify.cesr.exceptions.material.InvalidValueException;
import org.cardanofoundation.signify.cesr.exceptions.validation.ValidationException;
import org.cardanofoundation.signify.cesr.util.CoreUtil;
Expand Down Expand Up @@ -248,7 +249,10 @@ public Map<String, Object> rotate(String bran, List<Map<String, Object>> aids) t
if (aid.containsKey("salty")) {
Map<String, Object> salty = Utils.toMap(aid.get("salty"));
Cipher cipher = new Cipher(salty.get("sxlt").toString());
String dnxt = ((Salter) decrypter.decrypt(null, cipher)).getQb64();
DecryptResult result = decrypter.decrypt(null, cipher);
String dnxt = result.getSalter()
.map(Salter::getQb64)
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSalter but got DecryptedSigner"));

// Now we have the AID salt, use it to verify against the current public keys
Manager.SaltyCreator acreator = new Manager.SaltyCreator(
Expand Down Expand Up @@ -293,7 +297,9 @@ public Map<String, Object> rotate(String bran, List<Map<String, Object>> aids) t

for (String prx : prxs) {
Cipher cipher = new Cipher(prx);
Signer dsigner = (Signer) decrypter.decrypt(null, cipher, true);
DecryptResult result = decrypter.decrypt(null, cipher, true);
Signer dsigner = result.getSigner()
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSigner but got DecryptedSalter"));
signers.add(dsigner);
nprxs.add(encrypter.encrypt(dsigner.getQb64().getBytes()).getQb64());
}
Expand Down Expand Up @@ -333,7 +339,10 @@ public Map<String, Object> rotate(String bran, List<Map<String, Object>> aids) t

public String recrypt(String enc, Decrypter decrypter, Encrypter encrypter) throws LibsodiumException {
Cipher cipher = new Cipher(enc);
String dnxt = ((Salter) decrypter.decrypt(null, cipher)).getQb64();
DecryptResult result = decrypter.decrypt(null, cipher);
String dnxt = result.getSalter()
.map(Salter::getQb64)
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSalter but got DecryptedSigner"));
return encrypter.encrypt(dnxt.getBytes()).getQb64();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public Cipher(byte[] qb64b) {
super(qb64b);
}

public Object decrypt(byte[] prikey, byte[] seed) throws LibsodiumException {
public DecryptResult decrypt(byte[] prikey, byte[] seed) throws LibsodiumException {
Decrypter decrypter;
if(prikey != null) {
decrypter = new Decrypter(new String(prikey));
Expand Down
145 changes: 145 additions & 0 deletions src/main/java/org/cardanofoundation/signify/cesr/DecryptResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package org.cardanofoundation.signify.cesr;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

/**
* Sealed interface representing the result of a decryption operation.
* A decryption can produce either a Salter or a Signer.
*
* <p>This interface provides various helper methods to work with the result
* without explicit type checking or casting:
* <ul>
* <li>Type checking: {@link #isSalter()}, {@link #isSigner()}</li>
* <li>Safe extraction: {@link #getSalter()}, {@link #getSigner()}</li>
* <li>Visitor pattern: {@link #handle(Consumer, Consumer)}</li>
* <li>Functional mapping: {@link #map(Function, Function)}</li>
* </ul>
*
* <p>Example usage with pattern matching (Java 17+):
* <pre>{@code
* DecryptResult result = decrypter.decrypt(cipher, salt);
*
* // Pattern matching with switch
* switch (result) {
* case DecryptedSalter(var salter) -> System.out.println("Salter: " + salter.getQb64());
* case DecryptedSigner(var signer) -> System.out.println("Signer: " + signer.getQb64());
* }
*
* // Or with traditional instanceof
* if (result instanceof DecryptedSalter(var salter)) {
* // Use salter directly
* }
* }</pre>
*
* <p>Example usage with helper methods:
* <pre>{@code
* // Using visitor pattern
* result.handle(
* salter -> System.out.println("Got salter: " + salter),
* signer -> System.out.println("Got signer: " + signer)
* );
*
* // Using map for transformation
* String qb64 = result.map(
* salter -> salter.getQb64(),
* signer -> signer.getQb64()
* );
*
* // Using safe extraction
* result.getSalter().ifPresent(salter -> {
* // Work with salter
* });
* }</pre>
*/
public sealed interface DecryptResult permits DecryptResult.DecryptedSalter, DecryptResult.DecryptedSigner {

/**
* Checks if this result contains a Salter.
*
* @return true if this is a DecryptedSalter, false otherwise
*/
default boolean isSalter() {
return this instanceof DecryptedSalter;
}

/**
* Checks if this result contains a Signer.
*
* @return true if this is a DecryptedSigner, false otherwise
*/
default boolean isSigner() {
return this instanceof DecryptedSigner;
}

/**
* Returns the Salter if this result contains one.
*
* @return Optional containing the Salter, or empty if this is a Signer
*/
default Optional<Salter> getSalter() {
return this instanceof DecryptedSalter ds ? Optional.of(ds.salter()) : Optional.empty();
}

/**
* Returns the Signer if this result contains one.
*
* @return Optional containing the Signer, or empty if this is a Salter
*/
default Optional<Signer> getSigner() {
return this instanceof DecryptedSigner ds ? Optional.of(ds.signer()) : Optional.empty();
}

/**
* Handles this result by invoking the appropriate consumer.
* This is a visitor pattern implementation.
*
* @param salterHandler Consumer to invoke if this is a Salter
* @param signerHandler Consumer to invoke if this is a Signer
*/
default void handle(Consumer<Salter> salterHandler, Consumer<Signer> signerHandler) {
switch (this) {
case DecryptedSalter(var salter) -> salterHandler.accept(salter);
case DecryptedSigner(var signer) -> signerHandler.accept(signer);
}
}

/**
* Maps this result to a value of type T by applying the appropriate function.
*
* @param salterMapper Function to apply if this is a Salter
* @param signerMapper Function to apply if this is a Signer
* @param <T> The type of the result
* @return The result of applying the appropriate mapper
*/
default <T> T map(Function<Salter, T> salterMapper, Function<Signer, T> signerMapper) {
return switch (this) {
case DecryptedSalter(var salter) -> salterMapper.apply(salter);
case DecryptedSigner(var signer) -> signerMapper.apply(signer);
};
}

/**
* Record representing a decrypted Salter.
*/
record DecryptedSalter(Salter salter) implements DecryptResult {
public DecryptedSalter {
if (salter == null) {
throw new IllegalArgumentException("Salter cannot be null");
}
}
}

/**
* Record representing a decrypted Signer.
*/
record DecryptedSigner(Signer signer) implements DecryptResult {
public DecryptedSigner {
if (signer == null) {
throw new IllegalArgumentException("Signer cannot be null");
}
}
}
}

12 changes: 6 additions & 6 deletions src/main/java/org/cardanofoundation/signify/cesr/Decrypter.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private void setDecrypter() {
}
}

public Object decrypt(byte[] ser, Cipher cipher, Boolean transferable) throws LibsodiumException {
public DecryptResult decrypt(byte[] ser, Cipher cipher, Boolean transferable) throws LibsodiumException {
if (ser == null && cipher == null) {
throw new EmptyMaterialException("Neither ser nor matter are provided.");
}
Expand All @@ -47,11 +47,11 @@ public Object decrypt(byte[] ser, Cipher cipher, Boolean transferable) throws Li
return decrypter.decrypt(cipher, this.getRaw(), transferable != null && transferable);
}

public Object decrypt(byte[] ser, Cipher cipher) throws LibsodiumException {
public DecryptResult decrypt(byte[] ser, Cipher cipher) throws LibsodiumException {
return decrypt(ser, cipher, false);
}

private Object _x25519(Cipher cipher, byte[] priKey, Boolean transferable) throws LibsodiumException {
private DecryptResult _x25519(Cipher cipher, byte[] priKey, Boolean transferable) throws LibsodiumException {
Key pubKey = lazySodium.cryptoScalarMultBase(Key.fromBytes(priKey));
byte[] plain = new byte[cipher.getRaw().length - CRYPTO_BOX_SEAL_BYTES];
boolean success = lazySodium.cryptoBoxSealOpen(
Expand All @@ -65,16 +65,16 @@ private Object _x25519(Cipher cipher, byte[] priKey, Boolean transferable) throw
throw new LibsodiumException("Decryption failed");
}
if (cipher.getCode().equals(Codex.MatterCodex.X25519_Cipher_Salt.getValue())) {
return new Salter(plain);
return new DecryptResult.DecryptedSalter(new Salter(plain));
} else if (cipher.getCode().equals(Codex.MatterCodex.X25519_Cipher_Seed.getValue())) {
return new Signer(plain, transferable != null && transferable);
return new DecryptResult.DecryptedSigner(new Signer(plain, transferable != null && transferable));
} else {
throw new UnexpectedCodeException("Unsupported cipher text code = " + cipher.getCode());
}
}

@FunctionalInterface
private interface DecrypterFunction {
Object decrypt(Cipher cipher, byte[] priKey, Boolean transferable) throws LibsodiumException;
DecryptResult decrypt(Cipher cipher, byte[] priKey, Boolean transferable) throws LibsodiumException;
}
}
40 changes: 19 additions & 21 deletions src/main/java/org/cardanofoundation/signify/cesr/Keeping.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,23 +261,14 @@ public SaltyKeeper(
this.bran = null;
this.sxlt = sxlt;
Cipher ciph = new Cipher(this.sxlt);
Object decrypted = this.decrypter.decrypt(null, ciph, null);

if (ciph.getCode().equals(MatterCodex.X25519_Cipher_Salt.getValue())) {
this.creator = new Manager.SaltyCreator(
((Salter) decrypted).getQb64(),
tier,
this.stem
);
} else if (ciph.getCode().equals(MatterCodex.X25519_Cipher_Seed.getValue())) {
this.creator = new Manager.SaltyCreator(
((Signer) decrypted).getQb64(),
tier,
this.stem
);
} else {
throw new UnexpectedCodeException("Unsupported cipher text code = " + ciph.getCode());
}
DecryptResult decrypted = this.decrypter.decrypt(null, ciph, null);

// Use map() to get qb64 from either Salter or Signer
String qb64 = decrypted.map(
Salter::getQb64,
Signer::getQb64
);
this.creator = new Manager.SaltyCreator(qb64, tier, this.stem);
}

this.signers = this.creator.create(
Expand Down Expand Up @@ -508,11 +499,14 @@ public RandyKeeper(

this.signers = new ArrayList<>();
for (String prx : this.prxs) {
this.signers.add((Signer) this.decrypter.decrypt(
DecryptResult result = this.decrypter.decrypt(
new Cipher(prx).getQb64b(),
null,
this.transferable
));
);
Signer decryptedSigner = result.getSigner()
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSigner but got DecryptedSalter"));
this.signers.add(decryptedSigner);
}
}

Expand Down Expand Up @@ -576,11 +570,13 @@ public KeeperResult rotate(
List<String> verfers = new ArrayList<>();

for (String ntx : this.nxts) {
Signer signer = (Signer) this.decrypter.decrypt(
DecryptResult result = this.decrypter.decrypt(
null,
new Cipher(ntx),
this.transferable
);
Signer signer = result.getSigner()
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSigner but got DecryptedSalter"));
verfers.add(signer.getVerfer().getQb64());
}

Expand Down Expand Up @@ -610,11 +606,13 @@ public SignResult sign(
) throws LibsodiumException {
List<Signer> signers = new ArrayList<>();
for (String prx : this.prxs) {
Signer signer = (Signer) this.decrypter.decrypt(
DecryptResult result = this.decrypter.decrypt(
new Cipher(prx).getQb64b(),
null,
this.transferable
);
Signer signer = result.getSigner()
.orElseThrow(() -> new UnexpectedCodeException("Expected DecryptedSigner but got DecryptedSalter"));
signers.add(signer);
}

Expand Down
21 changes: 17 additions & 4 deletions src/main/java/org/cardanofoundation/signify/core/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ public String getSalt() throws LibsodiumException {
return this.salt;
} else {
String salt = this.ks.getGbls("salt");
return ((Matter) this.decrypter.decrypt(salt.getBytes(), null)).getQb64();
DecryptResult result = this.decrypter.decrypt(salt.getBytes(), null);
// Use map() to extract qb64 regardless of type
return result.map(
Salter::getQb64,
Signer::getQb64
);
}
}

Expand Down Expand Up @@ -464,7 +469,11 @@ public ManagerRotateResult rotate(RotateArgs args) throws DigestException, Libso
// If you provded a Salt for an AID but don't have encryption, pitch a fit
throw new RuntimeException("Invalid configuration: AID salt with no encryption");
}
salt = ((Matter) this.decrypter.decrypt(salt.getBytes(), null)).getQb64();
DecryptResult result = this.decrypter.decrypt(salt.getBytes(), null);
salt = result.map(
Salter::getQb64,
Signer::getQb64
);
} else {
salt = this.getSalt();
}
Expand Down Expand Up @@ -1122,7 +1131,9 @@ public List<Map.Entry<String, Signer>> prisElements(Decrypter decrypter) throws
List<Map.Entry<String, Signer>> entries = new ArrayList<>();
for (Map.Entry<String, byte[]> entry : pris.entrySet()) {
Verfer verfer = new Verfer(entry.getKey());
Signer signer = (Signer) decrypter.decrypt(entry.getValue(), null, verfer.isTransferable());
DecryptResult result = decrypter.decrypt(entry.getValue(), null, verfer.isTransferable());
Signer signer = result.getSigner()
.orElseThrow(() -> new RuntimeException("Expected DecryptedSigner but got DecryptedSalter"));
entries.add(new AbstractMap.SimpleEntry<>(entry.getKey(), signer));
}
return entries;
Expand All @@ -1148,7 +1159,9 @@ public Signer getPris(String pubKey, Decrypter decrypter) throws LibsodiumExcept
return null;
}
Verfer verfer = new Verfer(pubKey);
return (Signer) decrypter.decrypt(val, null, verfer.isTransferable());
DecryptResult result = decrypter.decrypt(val, null, verfer.isTransferable());
return result.getSigner()
.orElseThrow(() -> new RuntimeException("Expected DecryptedSigner but got DecryptedSalter"));
}

public boolean pinPths(String pubKey, PubPath val) {
Expand Down
Loading