mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
Create EncryptedMultisigDescriptor.java
This enhances multisig privacy by encrypting the descriptor with public-key-derived symmetric keys, preventing balance exposure. Advantages: Simplifies setup for individuals without private keys or Shamir sharing; enables wallet access via any two zpubs; maintains security as decryption requires matching pairs; avoids revealing full setup to single parties.
This commit is contained in:
parent
fa10714844
commit
d8b3672711
1 changed files with 70 additions and 0 deletions
|
|
@ -0,0 +1,70 @@
|
|||
// Import necessary Java libraries for encoding, cryptography, and utilities
|
||||
import java.nio.charset.StandardCharsets; // For UTF-8 charset handling
|
||||
import java.security.MessageDigest; // For SHA-256 hashing
|
||||
import java.security.SecureRandom; // For generating secure random IVs
|
||||
import java.util.Arrays; // For array operations like sorting
|
||||
import java.util.Base64; // For Base64 encoding/decoding
|
||||
import javax.crypto.Cipher; // For encryption/decryption operations
|
||||
import javax.crypto.SecretKeySpec; // For creating AES keys
|
||||
import javax.crypto.spec.GCMParameterSpec; // For GCM mode parameters
|
||||
import javax.crypto.AEADBadTagException; // For handling authentication failures in GCM
|
||||
|
||||
// Define the public class for encrypted multisig descriptors
|
||||
public class EncryptedMultisigDescriptor {
|
||||
|
||||
// Constants for IV and authentication tag lengths in GCM mode
|
||||
private static final int IV_LENGTH = 12; // Standard IV size for AES-GCM
|
||||
private static final int TAG_LENGTH = 16; // Standard tag size for authentication
|
||||
|
||||
// Method to generate a symmetric key from two zpubs by hashing their sorted concatenation
|
||||
public static byte[] getPairKey(String zpub1, String zpub2) throws Exception {
|
||||
String[] pair = {zpub1, zpub2}; // Create array of the two zpubs
|
||||
Arrays.sort(pair); // Sort to ensure consistent order regardless of input
|
||||
String concat = pair[0] + pair[1]; // Concatenate sorted zpubs
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256"); // Get SHA-256 digest instance
|
||||
return md.digest(concat.getBytes(StandardCharsets.UTF_8)); // Hash concatenation and return bytes
|
||||
}
|
||||
|
||||
// Method to encrypt a descriptor string using AES-GCM with the given key
|
||||
public static String encrypt(String descriptor, byte[] key) throws Exception {
|
||||
SecretKeySpec skey = new SecretKeySpec(key, "AES"); // Create AES key from byte array
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // Get AES-GCM cipher instance
|
||||
byte[] iv = new byte[IV_LENGTH]; // Create IV array
|
||||
new SecureRandom().nextBytes(iv); // Fill IV with secure random bytes
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH * 8, iv); // Create GCM spec with IV and tag bits
|
||||
cipher.init(Cipher.ENCRYPT_MODE, skey, spec); // Initialize cipher for encryption
|
||||
byte[] ct = cipher.doFinal(descriptor.getBytes(StandardCharsets.UTF_8)); // Encrypt descriptor to ciphertext
|
||||
byte[] tag = cipher.getAuthenticationTag(); // Get authentication tag (unused here, but typically from doFinal)
|
||||
byte[] blob = new byte[IV_LENGTH + TAG_LENGTH + ct.length]; // Create output blob array
|
||||
System.arraycopy(iv, 0, blob, 0, IV_LENGTH); // Copy IV to start of blob
|
||||
System.arraycopy(tag, 0, blob, IV_LENGTH, TAG_LENGTH); // Copy tag after IV
|
||||
System.arraycopy(ct, 0, blob, IV_LENGTH + TAG_LENGTH, ct.length); // Copy ciphertext after tag
|
||||
return Base64.getEncoder().encodeToString(blob); // Base64 encode blob and return as string
|
||||
}
|
||||
|
||||
// Method to decrypt an encrypted blob string using AES-GCM with the given key
|
||||
public static String decrypt(String blobStr, byte[] key) throws Exception {
|
||||
byte[] blob = Base64.getDecoder().decode(blobStr); // Decode Base64 blob to bytes
|
||||
if (blob.length < IV_LENGTH + TAG_LENGTH) throw new IllegalArgumentException(); // Check minimum length
|
||||
byte[] iv = Arrays.copyOfRange(blob, 0, IV_LENGTH); // Extract IV from blob
|
||||
byte[] tag = Arrays.copyOfRange(blob, IV_LENGTH, IV_LENGTH + TAG_LENGTH); // Extract tag
|
||||
byte[] ct = Arrays.copyOfRange(blob, IV_LENGTH + TAG_LENGTH, blob.length); // Extract ciphertext
|
||||
SecretKeySpec skey = new SecretKeySpec(key, "AES"); // Create AES key
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // Get cipher instance
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH * 8, iv); // Create GCM spec
|
||||
cipher.init(Cipher.DECRYPT_MODE, skey, spec); // Initialize for decryption
|
||||
cipher.setAuthenticationTag(tag); // Set expected tag for verification (GCM-specific)
|
||||
try {
|
||||
return new String(cipher.doFinal(ct), StandardCharsets.UTF_8); // Decrypt and return string
|
||||
} catch (AEADBadTagException e) {
|
||||
return null; // Failed decryption due to tag mismatch
|
||||
}
|
||||
}
|
||||
|
||||
// Comment block: Example usage for encryption and decryption in a 2-of-3 setup
|
||||
// Usage example: String[] zpubs = {...}; String descriptor = "...";
|
||||
// String[] blobs = new String[3];
|
||||
// blobs[0] = encrypt(descriptor, getPairKey(zpubs[0], zpubs[1]));
|
||||
// etc.
|
||||
// To decrypt: given zpubA, zpubB, try decrypt each blob with getPairKey(A,B), return first non-null.
|
||||
}
|
||||
Loading…
Reference in a new issue