├── README.md └── src └── se └── moar └── bitcoin └── multisig └── MultisigPresentation.java /README.md: -------------------------------------------------------------------------------- 1 | # Multisig example 2 | A simple example on how to use multisig with P2SH, pay to script hash 3 | 4 | # Libs needed to run this 5 | - [bitcoinj]: google bitcoin library 6 | - [guava]: google core libraries 7 | 8 | ## Important notes 9 | 10 | This code is only educational. The purpose is to understand how to build and use multisig transactions. If you want to use this code for a project, feel free do to so, but keep in mind there is plenty of things needed if you want to use this in production. If you run the code as it is, it will build and sign a transaction with one input and the default output is 0.1 mBTC or 100 bits. The input you use should have 0.2 mBTC or 200 bits on it. Leaving 0.1 mBTC as mining fee. The whole input gets spended, so if you are not careful you will end up with a huge mining fee. 11 | 12 | [bitcoinj]:https://github.com/bitcoinj/bitcoinj 13 | [guava]:https://github.com/google/guava 14 | -------------------------------------------------------------------------------- /src/se/moar/bitcoin/multisig/MultisigPresentation.java: -------------------------------------------------------------------------------- 1 | package se.moar.bitcoin.multisig; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.security.SecureRandom; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Iterator; 10 | import java.util.List; 11 | 12 | import org.bitcoinj.core.Address; 13 | import org.bitcoinj.core.AddressFormatException; 14 | import org.bitcoinj.core.Coin; 15 | import org.bitcoinj.core.DumpedPrivateKey; 16 | import org.bitcoinj.core.ECKey; 17 | import org.bitcoinj.core.NetworkParameters; 18 | import org.bitcoinj.core.Sha256Hash; 19 | import org.bitcoinj.core.Transaction; 20 | import org.bitcoinj.core.TransactionInput; 21 | import org.bitcoinj.crypto.TransactionSignature; 22 | import org.bitcoinj.params.MainNetParams; 23 | import org.bitcoinj.params.TestNet3Params; 24 | import org.bitcoinj.script.Script; 25 | import org.bitcoinj.script.ScriptBuilder; 26 | import org.bitcoinj.script.ScriptChunk; 27 | 28 | import com.google.common.collect.ImmutableList; 29 | 30 | @SuppressWarnings("unused") 31 | public class MultisigPresentation { 32 | 33 | // static final NetworkParameters params = TestNet3Params.get(); 34 | static final NetworkParameters params = MainNetParams.get(); 35 | static final Coin transactionFee = Coin.valueOf(10000); // 0.000 100 00 BTC 36 | 37 | /** 38 | * @param args 39 | * @throws AddressFormatException 40 | */ 41 | public static void main(String[] args) throws AddressFormatException { 42 | 43 | generateMultisig(); 44 | String getSignedTransaction = signFirstTime(); 45 | signSecondTime(getSignedTransaction); 46 | 47 | } 48 | 49 | private static void generateMultisig() throws AddressFormatException 50 | { 51 | ECKey key1 = createKeyFromSha256Passphrase("Super secret key 1"); 52 | /* 53 | DumpedPrivateKey privateKey1 = key1.getPrivateKeyEncoded(params); 54 | Address address1 = key1.toAddress(params); 55 | System.out.println(byteArrayToHex(key1.getPubKey())); // Print the public key 56 | System.out.println(privateKey1.toString() ); // Print the private key if you feel like it 57 | */ 58 | 59 | ECKey key2 = createKeyFromSha256Passphrase("Super secret key 2"); 60 | /* 61 | DumpedPrivateKey privateKey2 = key2.getPrivateKeyEncoded(params); 62 | Address address2 = key1.toAddress(params); 63 | System.out.println(byteArrayToHex(key2.getPubKey())); // Print the public key 64 | System.out.println(privateKey2.toString()); // Print the private key if you feel like it 65 | */ 66 | 67 | ECKey key3 = createKeyFromSha256Passphrase("Super secret key 3"); 68 | /* 69 | DumpedPrivateKey privateKey3 = key3.getPrivateKeyEncoded(params); 70 | Address address3 = key1.toAddress(params); 71 | System.out.println(byteArrayToHex(key3.getPubKey())); // Print the public key 72 | System.out.println(privateKey3.toString()); // Print the private key if you feel like it 73 | */ 74 | 75 | // Create a 2-of-3 multisig redeemScript (output script) 76 | // The private keys are not needed. The redeem script can be created with only public keys 77 | // Create a public key using new ECKey(null, publicKey) 78 | List keys = ImmutableList.of(key1, key2, key3); 79 | Script redeemScript = ScriptBuilder.createRedeemScript(2, keys); 80 | Script script = ScriptBuilder.createP2SHOutputScript(redeemScript); 81 | 82 | // Print the scripthash 83 | System.out.println("Redeem script: " + byteArrayToHex(redeemScript.getProgram())); 84 | 85 | // Print out our newly generated multisig address so people can send us coins 86 | Address multisig = Address.fromP2SHScript(params, script); 87 | System.out.println("Multisig address: " + multisig.toString()); 88 | 89 | } 90 | 91 | private static String signFirstTime() throws AddressFormatException 92 | { 93 | // Generate a private key from our super secret passphrase 94 | ECKey key1 = createKeyFromSha256Passphrase("Super secret key 1"); 95 | 96 | // Use the redeem script we have saved somewhere to start building the transaction 97 | Script redeemScript = new Script(hexStringToByteArray("524104711acc7644d34e493eba76984c81d99f1233f06b3242d90e6cd082b26fd0c1186f65de8d3378a6630f2285bd17972372685378683b604c68343fa1b532196c4d410476d6ef11a42010a889ee0c3d75f9cac3a51a3e245744fb9bf1bc8c196eb0f6982e39aad753514248966f4d545a5439ece8e27e13764c92f6230e0244cae5bee54104a45f0da4e6501fa781b6534e601f410a59328691d86d034d13362138f7e9a2927451280544e36c88279ee00c7face2fb707d0210842017e3937ae4584faacf6753ae")); 98 | 99 | // Start building the transaction by adding the unspent inputs we want to use 100 | // The data is taken from blockchain.info, and can be found here: https://blockchain.info/rawtx/ca1884b8f2e0ba88249a86ec5ddca04f937f12d4fac299af41a9b51643302077 101 | Transaction spendTx = new Transaction(params); 102 | ScriptBuilder scriptBuilder = new ScriptBuilder(); 103 | scriptBuilder.data(new String("a9144f93910f309e2433c25d1e891e29fd4cec8c5f6187").getBytes()); // Script of this output 104 | TransactionInput input = spendTx.addInput(new Sha256Hash("19f589be5fda5a97b5a26158abd1fa02e68a15e5a6a4d83791935f882dbe0492"), 0, scriptBuilder.build()); 105 | 106 | // Add outputs to the person receiving bitcoins 107 | Address receiverAddress = new Address(params, "1Magmaqvxx7LpUTYGWw8RNPEJXBQ6iSLVX"); 108 | Coin charge = Coin.valueOf(10000); // 0.1 mBTC 109 | Script outputScript = ScriptBuilder.createOutputScript(receiverAddress); 110 | spendTx.addOutput(charge, outputScript); 111 | 112 | // Sign the first part of the transaction using private key #1 113 | Sha256Hash sighash = spendTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); 114 | ECKey.ECDSASignature ecdsaSignature = key1.sign(sighash); 115 | TransactionSignature transactionSignarture = new TransactionSignature(ecdsaSignature, Transaction.SigHash.ALL, false); 116 | 117 | // Create p2sh multisig input script 118 | Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(Arrays.asList(transactionSignarture), redeemScript); 119 | 120 | // Add the script signature to the input 121 | input.setScriptSig(inputScript); 122 | System.out.println(byteArrayToHex(spendTx.bitcoinSerialize())); 123 | 124 | return byteArrayToHex(spendTx.bitcoinSerialize()); 125 | } 126 | 127 | private static String signSecondTime(String transactionString) 128 | { 129 | // Take the hex string we got from the other part and convert it into a Transaction object 130 | Transaction spendTx = new Transaction(params, hexStringToByteArray(transactionString)); 131 | 132 | // Get the input chunks 133 | Script inputScript = spendTx.getInput(0).getScriptSig(); 134 | List scriptChunks = inputScript.getChunks(); 135 | 136 | // Create a list of all signatures. Start by extracting the existing ones from the list of script schunks. 137 | // The last signature in the script chunk list is the redeemScript 138 | List signatureList = new ArrayList(); 139 | Iterator iterator = scriptChunks.iterator(); 140 | Script redeemScript = null; 141 | 142 | while (iterator.hasNext()) 143 | { 144 | ScriptChunk chunk = iterator.next(); 145 | 146 | if (iterator.hasNext() && chunk.opcode != 0) 147 | { 148 | TransactionSignature transactionSignarture = TransactionSignature.decodeFromBitcoin(chunk.data, false); 149 | signatureList.add(transactionSignarture); 150 | } else 151 | { 152 | redeemScript = new Script(chunk.data); 153 | } 154 | } 155 | 156 | // Create the sighash using the redeem script 157 | Sha256Hash sighash = spendTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); 158 | ECKey.ECDSASignature secondSignature; 159 | 160 | // Take out the key and sign the signhash 161 | ECKey key2 = createKeyFromSha256Passphrase("Super secret key 2"); 162 | secondSignature = key2.sign(sighash); 163 | 164 | // Add the second signature to the signature list 165 | TransactionSignature transactionSignarture = new TransactionSignature(secondSignature, Transaction.SigHash.ALL, false); 166 | signatureList.add(transactionSignarture); 167 | 168 | // Rebuild p2sh multisig input script 169 | inputScript = ScriptBuilder.createP2SHMultiSigInputScript(signatureList, redeemScript); 170 | spendTx.getInput(0).setScriptSig(inputScript); 171 | 172 | System.out.println(byteArrayToHex(spendTx.bitcoinSerialize())); 173 | 174 | return byteArrayToHex(spendTx.bitcoinSerialize()); 175 | } 176 | 177 | /** 178 | * Method to convert a passphrase similar to brainwallet.org, to a bitcoin private key. This method of creating an ECKey is deprecated 179 | * @param secret 180 | * @return 181 | */ 182 | public static ECKey createKeyFromSha256Passphrase(String secret) { 183 | byte[] hash = null; 184 | 185 | try { 186 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 187 | md.update(secret.getBytes("UTF-8")); 188 | hash = md.digest(); 189 | } catch (NoSuchAlgorithmException e) { 190 | e.printStackTrace(); 191 | } catch (UnsupportedEncodingException e) { 192 | e.printStackTrace(); 193 | } 194 | @SuppressWarnings("deprecation") 195 | ECKey key = new ECKey(hash, (byte[])null); 196 | return key; 197 | } 198 | 199 | /** 200 | * Method to convert a passphrase similar to a bitcoin private key. Not the same as brainwallet.org 201 | * @param secret 202 | * @return 203 | */ 204 | public static ECKey createFromPassphrase(String secret) { 205 | byte[] hash = null; 206 | 207 | try { 208 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 209 | md.update(secret.getBytes("UTF-8")); 210 | hash = md.digest(); 211 | } catch (NoSuchAlgorithmException e) { 212 | e.printStackTrace(); 213 | } catch (UnsupportedEncodingException e) { 214 | e.printStackTrace(); 215 | } 216 | ECKey key = new ECKey(new SecureRandom(hash)); 217 | return key; 218 | } 219 | 220 | /** 221 | * Method to convert a byte array to human readable hex string 222 | * @param a 223 | * @return hex string 224 | */ 225 | public static String byteArrayToHex(byte[] a) { 226 | StringBuilder sb = new StringBuilder(a.length * 2); 227 | for(byte b: a) 228 | sb.append(String.format("%02x", b & 0xff)); 229 | return sb.toString(); 230 | } 231 | 232 | /** 233 | * Method to convert a human readable hex string to a byte array 234 | * @param s 235 | * @return byte array 236 | */ 237 | public static byte[] hexStringToByteArray(String s) { 238 | int len = s.length(); 239 | byte[] data = new byte[len / 2]; 240 | for (int i = 0; i < len; i += 2) { 241 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 242 | + Character.digit(s.charAt(i+1), 16)); 243 | } 244 | return data; 245 | } 246 | 247 | } 248 | --------------------------------------------------------------------------------