-
-
Save kac-/6b19a3088765ee9efc21 to your computer and use it in GitHub Desktop.
peermessage-mod-kac
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void sendMessageToBob() throws Exception { | |
Charset UTF8 = Charset.forName("UTF-8"); | |
String MAC_ALGO = "HmacSHA1"; | |
Mac MAC = Mac.getInstance(MAC_ALGO); | |
MessageDigest SHA512 = MessageDigest.getInstance("SHA-512"); | |
Cipher AES = Cipher.getInstance("AES/CBC/PKCS5Padding"); | |
byte[] prefix = "pmsg-mod-kac".getBytes(); | |
NetworkParameters params = PPCNetParams.get(); | |
ECKey alice = new DumpedPrivateKey(params, | |
"U5AJjxbyWtWpUtcfqUHj2tAgUXhd35eaX6qGz6wW1cE9RgRRLCSy").getKey(); | |
ECKey bob = new DumpedPrivateKey(params, | |
"U8xAm8bnf7wvLVdNDm3sXZ1msb8JNJyAfTMNNrehfHrf47HaSobu").getKey(); | |
List<Entry<byte[], byte[]>> messages = new ArrayList<Map.Entry<byte[], byte[]>>(); | |
messages.add(new AbstractMap.SimpleEntry("Hi Bob, how are you? Let's meet".getBytes(UTF8), | |
bob.getPubKey())); | |
BigInteger feePerKB = BigInteger.valueOf(10 * 1000); | |
BigInteger minOutputValue = BigInteger.valueOf(10 * 1000); | |
// load input transaction | |
byte[] prevRawTx = Hex | |
.decodeHex("0100000039f6b85401ffe493177fc3771964b2b661982a69acee6392aa51a81b30535d12cbcc77e442010000006c493046022100fb276784fe6184daca38be1ec32561f0277c51d5c3843f04f6e005194a1ca9b5022100e84baafc6b195281f6077ed207b25f688277184214d2ae1d0fe04e00911e00b10121039bb033187a0d664e53e351f585f9d90d7dd1088b34e1ac3a970d55c5f45c77edffffffff0200a60e00000000001976a914db3b97012dc875a408d7bab2ea61f20295b6ac7a88ac30750000000000001976a9149421bf3690377d9004b2f2e9d2e1951589f55c9d88ac00000000" | |
.toCharArray()); | |
Transaction prevTx = new Transaction(params, prevRawTx); | |
TransactionOutPoint prevOutPoint = new TransactionOutPoint(params, 1, prevTx.getHash()); | |
TransactionOutput prevOut = prevTx.getOutput((int) prevOutPoint.getIndex()); | |
Transaction tx = new Transaction(params); | |
tx.setTime(prevTx.getTime()); | |
tx.addInput(prevOut); | |
for (Entry<byte[], byte[]> e : messages) { | |
// http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme | |
byte[] m = e.getKey(); | |
if (m.length > 31) { | |
throw new RuntimeException("message too long"); | |
} | |
// generate initialization vector for AES/CBC from current output | |
// index and prev tx hash | |
SHA512.reset(); | |
SHA512.update(new VarInt(tx.getOutputs().size()).encode()); | |
SHA512.update(prevOutPoint.getHash().getBytes()); | |
byte[] IV = Arrays.copyOf(SHA512.digest(), 16); | |
// generates a random number | |
BigInteger r = alice.getPrivKey(); | |
// derives a shared secret | |
ECPoint K_B = ECKey.fromPublicOnly(e.getValue()).getPubKeyPoint(); | |
BigInteger S = K_B.multiply(r).normalize().getAffineXCoord().toBigInteger(); | |
// uses KDF to derive a symmetric encryption and a MAC keys | |
SHA512.reset(); | |
byte[] k_tmp = SHA512.digest(S.toByteArray()); | |
byte[] k_E = Arrays.copyOfRange(k_tmp, 0, 32); | |
byte[] k_M = Arrays.copyOfRange(k_tmp, 32, 64); | |
// encrypt the message | |
AES.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k_E, "AES"), new IvParameterSpec(IV)); | |
byte[] c = AES.doFinal(m); | |
// computes the tag of encrypted message | |
MAC.init(new SecretKeySpec(k_M, MAC_ALGO)); | |
byte[] d = MAC.doFinal(c); | |
// create ScriptPubkey | |
// from fake uncompressed public key | |
byte[] pubKey = new byte[65]; | |
pubKey[0] = 0x04; | |
System.arraycopy(prefix, 0, pubKey, 1, prefix.length); | |
System.arraycopy(c, 0, pubKey, 1 + prefix.length, c.length); | |
System.arraycopy(d, 0, pubKey, 1 + prefix.length + c.length, d.length); | |
tx.addOutput(new TransactionOutput(params, tx, minOutputValue, ECKey | |
.fromPublicOnly(pubKey))); | |
} | |
{ // sign input, adjust fee if needed | |
BigInteger available = prevOut.getValue(); | |
BigInteger paid = minOutputValue.multiply(BigInteger.valueOf(tx.getOutputs().size())); | |
BigInteger fee, feeReq = feePerKB, change; | |
TransactionOutput changeTx = null; | |
boolean changeOff = false; | |
do { | |
fee = feeReq; | |
change = available.subtract(fee).subtract(paid); | |
if (change.compareTo(BigInteger.ZERO) < 0) { | |
throw new RuntimeException("insufficient funds"); | |
} else if (changeOff) { | |
} else if (change.compareTo(minOutputValue) >= 0) { | |
if (changeTx == null) { | |
changeTx = tx.addOutput(change, alice.toAddress(params)); | |
} else { | |
changeTx.setValue(change); | |
} | |
} else if (changeTx != null) { | |
List<TransactionOutput> outs = tx.getOutputs(); | |
tx.clearOutputs(); | |
for (int i = 0; i < outs.size() - 1; i++) { | |
tx.addOutput(outs.get(i)); | |
} | |
changeTx = null; | |
changeOff = true; | |
} | |
TransactionSignature sig = tx.calculateSignature(0, alice, | |
prevOut.getScriptPubKey(), SigHash.ALL, false); | |
tx.getInput(0).setScriptSig(ScriptBuilder.createInputScript(sig, alice)); | |
feeReq = BigInteger.valueOf(tx.bitcoinSerialize().length / 1000 + 1).multiply( | |
feePerKB); | |
} while (feeReq.compareTo(fee) > 0); | |
} | |
// broadcast tx | |
// now Bob | |
ECKey R = ECKey.fromPublicOnly(tx.getInput(0).getScriptSig().getPubKey()); | |
// derives the shared secret | |
BigInteger S = R.getPubKeyPoint().multiply(bob.getPrivKey()).normalize().getAffineXCoord() | |
.toBigInteger(); | |
outputs: for (int o = 0; o < tx.getOutputs().size(); o++) { | |
TransactionOutput to = tx.getOutput(o); | |
if (to.getScriptPubKey().isSentToRawPubKey()) { | |
byte[] pubKey = to.getScriptPubKey().getPubKey(); | |
if (pubKey.length != 65) { // compressed | |
continue; | |
} | |
for (int i = 0; i < prefix.length; i++) { | |
if (pubKey[i + 1] != prefix[i]) { | |
continue outputs; | |
} | |
} | |
byte[] c = new byte[32]; | |
System.arraycopy(pubKey, 1 + prefix.length, c, 0, c.length); | |
byte[] d = new byte[20]; | |
System.arraycopy(pubKey, 1 + prefix.length + c.length, d, 0, d.length); | |
// derives keys the same way as Alice did | |
byte[] k_tmp = SHA512.digest(S.toByteArray()); | |
byte[] k_E = Arrays.copyOfRange(k_tmp, 0, 32); | |
byte[] k_M = Arrays.copyOfRange(k_tmp, 32, 64); | |
// uses MAC to check the tag | |
MAC.init(new SecretKeySpec(k_M, MAC_ALGO)); | |
byte[] d2 = MAC.doFinal(c); | |
if (!Arrays.equals(d, d2)) { | |
continue outputs; | |
} | |
// uses symmetric encryption scheme to decrypt the message | |
SHA512.reset(); | |
SHA512.update(new VarInt(o).encode()); | |
SHA512.update(prevOutPoint.getHash().getBytes()); | |
byte[] IV = Arrays.copyOf(SHA512.digest(), 16); | |
AES.init(Cipher.DECRYPT_MODE, new SecretKeySpec(k_E, "AES"), | |
new IvParameterSpec(IV)); | |
byte[] m = AES.doFinal(c); | |
System.out.println(R.toAddress(params) + ": " + new String(m, UTF8)); | |
} | |
} | |
System.out.println(Hex.encodeHexString(tx.bitcoinSerialize())); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Don't use it, fee-calculation is not finished.
Edit: fixed