Skip to content

Instantly share code, notes, and snippets.

@kac-
Last active August 29, 2015 14:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kac-/6b19a3088765ee9efc21 to your computer and use it in GitHub Desktop.
Save kac-/6b19a3088765ee9efc21 to your computer and use it in GitHub Desktop.
peermessage-mod-kac
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()));
}
@kac-
Copy link
Author

kac- commented Jan 17, 2015

Don't use it, fee-calculation is not finished.

Edit: fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment