Bitcoin Forum
May 05, 2024, 09:31:28 PM *
News: Latest Bitcoin Core release: 27.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: Anyone know the structure of a mutli-sig address?  (Read 1501 times)
gweedo (OP)
Legendary
*
Offline Offline

Activity: 1498
Merit: 1000


View Profile
March 09, 2014, 03:38:19 AM
 #1

I want to generate my own mutli-sig addresses using my public keys. How can I do this?  Just walk me thru what it takes to generate a mutli-sig address from public keys and n require value.

Honestly I just need to know how to insert the n required value into the address? I can generate them using the version bytes 0X05.
According to NIST and ECRYPT II, the cryptographic algorithms used in Bitcoin are expected to be strong until at least 2030. (After that, it will not be too difficult to transition to different algorithms.)
Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction.
cbeast
Donator
Legendary
*
Offline Offline

Activity: 1736
Merit: 1006

Let's talk governance, lipstick, and pigs.


View Profile
March 09, 2014, 03:57:13 AM
 #2

Here's a tutorial. https://bitcointalk.org/index.php?topic=270204.msg2892129#msg2892129

Any significantly advanced cryptocurrency is indistinguishable from Ponzi Tulips.
etotheipi
Legendary
*
expert
Offline Offline

Activity: 1428
Merit: 1093


Core Armory Developer


View Profile WWW
March 09, 2014, 06:30:53 AM
Last edit: March 09, 2014, 04:49:38 PM by etotheipi
 #3


I know how to generate them on bitcoin-qt but I am looking for the process of generating them. I want to write my own program that can generate them so I am looking for more of the step by step of hashing functions and what data needs to be put into those hash to create a mutli-sig address. I know you need the public keys uncompressed and the required n value and I know the version bytes are 0x05.

The bitcoin scripts are pretty simple, but making sure you're doing the right thing can be challenging -- for instance, you can create multi-sig script for receiving the money, but most tools don't display and validate the script properly.  You'll have to make sure you do that part correctly, too.  Luckily, if you do it wrong, the network likely won't propagate it (just don't mine it yourself!).

If you want to use P2SH, you would hash the output script and construct the address string using 0x05 instead of 0x00 as you would for a normal address.  But this is irrelevant if you're manually creating transactions, since the network never sees that prefix byte -- it's only an identifier for the address strings that are passed around between humans, but are not included in the actual scripts inserted into the blockchain.  

But if you're doing a lot of one-off multi-sig transactions, and not necessarily interacting with others (for now, maybe you just want to protect your own money with this tool you're creating), then I would recommend using vanilla multi-sig without P2SH.  This will make it easier to find your money if you later need to search the blockchain for multi-sig addresses that are relevant to your wallets (P2SH will hide those details).  A full-featured multi-sig solution (like Armory will hopefully do soon) will use multiple wallets with meta-data and a structured key selection that makes P2SH safe in this respect.  Until then, you can use plain multisig scripts which are standard up to 3-of-3.

Finally, I recommend you do all your testing on testnet.  Everything is identical, except if you do use P2SH, use the following prefix bytes for generating address strings:

   Regular Addresses = '\x6f'
   P2SH Addresses = '\xc4'



Onto the fun part -- this can all be done with armoryengine.  And the latest version of armoryengine also recognizes and displays P2SH addresses (on the "testing" branch), though it doesn't quite know what to do with multisig scripts.  Nonetheless, you can manually put together the pieces using https://bitcoinarmory.com/developers/python-scripting/ as a guide.

If I was going to do it, I would use regular multisig: I would probably use Armory GUI to create a transaction sending X coins to a regular address, but don't send it yet, create an unsigned transaction.  Then go hex-diving into that transaction and replace the TxOut script (and var_int preceeding it) with the multisig script.  In fact, you can create the script with my untested function pubkeylist_to_multisig_script(pubKeyList, Mreq).  Though, it's a very simple script:

Code:
OP_2  PubKeyA  PubKeyB  PubKeyC OP_3 OP_CHECKMULTISIG

If you're doing 2-of-3, get the three public keys, and put the binary forms of them into a list, something like:
Code:
pubkeys = ['']*3
pubkeys[0] = hex_to_binary('0438c80fa100871bc8115311d...')
pubkeys[1] = hex_to_binary('047613d073ff18301aab45df1...')
pubkeys[2] = hex_to_binary('040f83e3d9983aba4246fe3aa...')
msScript = pubkeylist_to_multisig_script(pubkeys, 2)
msScript = script_to_p2sh_script(msScript)  # OPTIONAL if you want P2SH
scriptSizeVarint = packVarInt(len(script))[0]

Now remove the var_int and script from the recipient txout (don't mess with the change output!), and put this varInt and script in there.  Unless I missed something, this should be a valid transaction.  You should be able to sign and broadcast it (you might even be able to do it with Armory as if it was an offline transaction, though it also might choke on the form of the receipient "address" -- let me know!).  

Spending it, is more challenging with the available tools, but it can be done.  The gist of it is that you will create the spending transaction, using as an input the unspent multi-sig (or P2SH) TxOut.  You will create the TxIn script using the standard form

Code:
 OP_0  SigAOrEmptyString SigBOrEmptyString SigCOrEmptyString

You will need the code here to produce the hash that needs to be signed for OP_CHECKSIG/OP_CHECKMULTSIG.  You will sign that hash with each of 2 of the 3 private keys, and put an empty string (OP_0?) in the other location (they need to be in same order as the keys in the TxOut script).  That should be a valid spend script.

Of course, I probably missed all sorts of small details, but it's late, so I'll have to defer discussion of those details until later.  Hopefully that's at least enough to get you started.

Founder and CEO of Armory Technologies, Inc.
Armory Bitcoin Wallet: Bringing cold storage to the average user!
Only use Armory software signed by the Armory Offline Signing Key (0x98832223)

Please donate to the Armory project by clicking here!    (or donate directly via 1QBDLYTDFHHZAABYSKGKPWKLSXZWCCJQBX -- yes, it's a real address!)
kjj
Legendary
*
Offline Offline

Activity: 1302
Merit: 1024



View Profile
March 11, 2014, 11:58:48 AM
 #4

Order matters.

Run out all 6 permutations to see if one matches.  (I always sort mine.)

17Np17BSrpnHCZ2pgtiMNnhjnsWJ2TMqq8
I routinely ignore posters with paid advertising in their sigs.  You should too.
piotr_n
Legendary
*
Offline Offline

Activity: 2053
Merit: 1354


aka tonikt


View Profile WWW
March 11, 2014, 09:25:54 PM
Last edit: March 11, 2014, 09:41:52 PM by piotr_n
 #5

Code:
{
"address" : "2Mxp2qwtpHCYxy7AEhtdzcX6JymvnjP7iEB",
"redeemScript" : "522103d7133580d6fb5e6c3b006d05cd44b60a84248afb1faae23a56c24e043bdef8d121022c170c543cc9ee2b4ee97302d4cce4cfcf4db2e8d0f5c172d2238895c49f60f42103cfff1686632906ecdf12d00cda0f90823f881a01e8bc8511d99288ca0d6dabfd53ae"
}

I am trying to do this in php so that maybe my first issue, but if I could just generate that redeemscript it would be trivial to create a multi-sig address.
redeemScript is pretty much straight forward:

* push the number of signatures required to spend (that's normally one byte opcode: value = 0x50 + the number)
Code:
52

* push a public key -  and do it N times, once for each key
Code:
21:03d7133580d6fb5e6c3b006d05cd44b60a84248afb1faae23a56c24e043bdef8d1
21:022c170c543cc9ee2b4ee97302d4cce4cfcf4db2e8d0f5c172d2238895c49f60f4
21:03cfff1686632906ecdf12d00cda0f90823f881a01e8bc8511d99288ca0d6dabfd

* push N (normally one byte: 0x50 + N)
Code:
53

* append opcode 0xae (whatever it does - at this stage nobody cares)
Code:
ae

to make an address out of such a script, just hash the entire redeemScript with rimpd160(sha256(script)) - this will give you 20 bytes.
to create the actual text address use a regular bitcoin's 20-bytes to base58 address "translation", with the 4 checksum bytes.
the version byte is different -I believe it's 5 for the real net (unlike 0 for regular addresses)

now, this part (generating a multisig address) - this is very simple.
but when you get to spending coins from it - then a real fun starts Smiley
whoever invented this system must had been a masochist Wink

Check out gocoin - my original project of full bitcoin node & cold wallet written in Go.
PGP fingerprint: AB9E A551 E262 A87A 13BB  9059 1BE7 B545 CDF3 FD0E
piotr_n
Legendary
*
Offline Offline

Activity: 2053
Merit: 1354


aka tonikt


View Profile WWW
March 11, 2014, 10:18:07 PM
Last edit: March 11, 2014, 10:38:05 PM by piotr_n
 #6

Just one question where does the 21 in front of each address come from?
It's an opcode from the script - means "push the next 0x21 bytes to the stack, as a new top element".
https://en.bitcoin.it/wiki/Script#Constants
If you had uncompressed public key (e.g. from some old wallet) - this would have been 0x41

Also I am going to do testing on to spend it but I will be using bitcoin-qt to do spending so I am not too worried.
I'm afraid it will still ask you for values which need for providing you will find quite ridiculous, concerning that this was supposed to be a function of a wallet... like: the private key Smiley

That actually might be the only wallet ever created, which needs to be provided with a private key from outside in order to sign a transaction - making it an extremely clever idea Smiley

Check out gocoin - my original project of full bitcoin node & cold wallet written in Go.
PGP fingerprint: AB9E A551 E262 A87A 13BB  9059 1BE7 B545 CDF3 FD0E
kjj
Legendary
*
Offline Offline

Activity: 1302
Merit: 1024



View Profile
March 12, 2014, 01:53:29 PM
 #7

Order matters.

Run out all 6 permutations to see if one matches.  (I always sort mine.)

How do you sort yours? I just been using lexicographically per etothepi's code.

Smallest to largest.

The point is that ABC gives a different redeemscript (and thus a different hash) than ACB, or any of the other 4 permutations.  I think I suggested that the RPC command to create a multisig p2sh address should sort the keys (with an optional flag to not sort them), but I don't know if it was ever done or not.

But if you are testing your code against the address created by bitcoind, you need to match whatever ordering bitcoind is using, or you may get a different (but possibly totally valid and usable) address.

Just one question where does the 21 in front of each address come from?
It's an opcode from the script - means "push the next 0x21 bytes to the stack, as a new top element".
https://en.bitcoin.it/wiki/Script#Constants

There are a bunch of opcodes that mean "push a new stack object made of this literal number of bytes which immediately follow".

In this case, the stack object is an encoded public key.  Public keys are encoded as follows:

0x02 (1 byte) - {pubkey.x} (32 bytes) (y value is implied and has even parity)
0x03 (1 byte) - {pubkey.x} (32 bytes) (y value is implied and has odd parity)
0x04 (1 byte) - {pubkey.x} (32 bytes) - {pubkey.y} (32 bytes) *

1 byte + 32 bytes = 33 bytes, which is 0x21 in hex, and the opcode map was created in a way that 0x21 is the opcode that means push 0x21 bytes.

If you are always using compressed pubkeys, you can probably get away with just using the literal 0x21 here, but for a more extensible script, you should probably write a generic cscript_pushobject function that is robust.

* Please don't ever use this one.

17Np17BSrpnHCZ2pgtiMNnhjnsWJ2TMqq8
I routinely ignore posters with paid advertising in their sigs.  You should too.
piotr_n
Legendary
*
Offline Offline

Activity: 2053
Merit: 1354


aka tonikt


View Profile WWW
March 12, 2014, 07:00:01 PM
 #8

anyway, if you ever get to actually spending coins from multisig addresses, here is what you'd like to know.

* while using "signrawtransaction" API, don't worry about the txid+vout param - the function requires you to specify it, but put there whatever you want and it will still work
* unfortunately (at least for multisig) this function requires you to specify the actual signing private key (that also needs to be in your wallet, BTW). at least I didn't manage to use it without giving it a private key, along with the request.

and abstracting from the bitcoin-qt:
* in the actual signed transaction, at the beginning of each sigscript there must be OP_FALSE opcode, which is supposedly a workaround for some bug
* the order of the actual signatures must match (follow) the order of the public keys from the p2sh script. if you change their order, it won't verify.

besides these few details it actually seems to work quite nice.

Check out gocoin - my original project of full bitcoin node & cold wallet written in Go.
PGP fingerprint: AB9E A551 E262 A87A 13BB  9059 1BE7 B545 CDF3 FD0E
fbueller
Sr. Member
****
Offline Offline

Activity: 412
Merit: 266


View Profile
March 13, 2014, 02:42:01 AM
Last edit: March 14, 2014, 05:48:11 PM by fbueller
 #9

Take care when generating the redeemscript to use the right varint in case you're given an uncompressed key.

Code:
	function create_redeem_script($m, $public_keys = array() ) {
if(count($public_keys) == 0)
return FALSE;
if($m == 0)
return FALSE;

$redeemScript = dechex(0x50+$m);
foreach($public_keys as $public_key) {
$redeemScript .= dechex(strlen($public_key)/2).$public_key;
}
$redeemScript .= dechex(0x50+(count($public_keys))).'ae';
return $redeemScript;
}

public function decode_redeem_script($redeem_script, $data = array()) {
// If there is no more work to be done (script is fully parsed,
// return the array)
if(strlen($redeem_script) == 0)
return $data;

// Fail if the redeem_script has an uneven number of characters.
if(strlen($redeem_script) % 2 !== 0)
return FALSE;

// First step is to get m, the required number of signatures
if(!isset($data['m']) || count($data) == 0) {
$data['m'] = gmp_strval(gmp_sub(gmp_init(substr($redeem_script, 0, 2),16),gmp_init('50',16)),10);
$data['keys'] = array();
$redeem_script = substr($redeem_script, 2);

} else if(count($data['keys']) == 0 && !isset($data['next_key_charlen'])) {
// Next is to find out the length of the following public key.
$hex = substr($redeem_script, 0, 2);
// Set up the length of the following key.
$data['next_key_charlen'] = gmp_strval(gmp_mul(gmp_init('2',10),gmp_init($hex, 16)),10);
$redeem_script = substr($redeem_script, 2);

} else if(isset($data['next_key_charlen'])) {
// Extract the key, and work out the next step for the code.
$data['keys'][] = substr($redeem_script, 0, $data['next_key_charlen']);
$next_op = substr($redeem_script, $data['next_key_charlen'], 2);
$redeem_script = substr($redeem_script, ($data['next_key_charlen']+2));
unset($data['next_key_charlen']);

// If 1 <= $next_op >= 4b
if( in_array(gmp_cmp(gmp_init($next_op, 16),gmp_init('1',16)),array('0','1'))
&& in_array(gmp_cmp(gmp_init($next_op, 16),gmp_init('4b', 16)),array('-1','0'))) {
// Set the next key character length
$data['next_key_charlen'] = gmp_strval(gmp_mul(gmp_init('2',10),gmp_init($next_op, 16)),10);

// If 52 <= $next_op >= 60
} else if( in_array(gmp_cmp(gmp_init($next_op, 16),gmp_init('52',16)),array('0','1'))
&& in_array(gmp_cmp(gmp_init($next_op, 16),gmp_init('60', 16)),array('-1','0'))) {
// Finish the script.
$data['n'] = gmp_strval(gmp_sub(gmp_init($next_op, 16),gmp_init('50',16)),10);
$redeem_script = '';
} else {
// Something weird, malformed redeemScript.
return FALSE;
}
}
return self::decode_redeem_script($redeem_script, $data);
}

Edit: Added recursive function to decode the redeemscript.

Bitwasp Developer.
instagibbs
Member
**
Offline Offline

Activity: 114
Merit: 12


View Profile
March 13, 2014, 04:08:16 AM
 #10

So is it true the uncompressed keys must be used? I'm also trying to reproduce what bitcoind's multisig implementation does, and am getting a different output address using compressed keys.

Bitalo_Maciej
Member
**
Offline Offline

Activity: 80
Merit: 10


Lead developer


View Profile WWW
March 13, 2014, 09:27:43 AM
 #11

So is it true the uncompressed keys must be used? I'm also trying to reproduce what bitcoind's multisig implementation does, and am getting a different output address using compressed keys.

As others said, check the order of your keys. It matters - different order will output a different address.

Web wallets get hacked all the time. Computer wallets get hacked all the time as well.
Solution? Hybrid P2SH wallets - safer than your online and offline wallets combined. Check it out, store and trade your Bitcoins with ease of mind!
instagibbs
Member
**
Offline Offline

Activity: 114
Merit: 12


View Profile
March 13, 2014, 01:47:49 PM
 #12

I'm doing a simple 2-of-2, tried both permutations. 

To make sure I didn't understand the previous 50 tutorials I've read, here's the hex bytes I'm hashing:

ripemd-160(sha256("2<pubk1><pubk2>2<OP_CHECKMULTSIG>"))

I'm pretty sure this is the step that's goofing it up, everything else is similar to standard addresses.
kjj
Legendary
*
Offline Offline

Activity: 1302
Merit: 1024



View Profile
March 13, 2014, 06:59:38 PM
 #13

Make a couple of bogus privkeys, use bitcoind to generate the 2-of-2 address, and use your script to generate it.  Post the keys, the redeemscripts (both of them) and the two resulting p2sh addresses.

17Np17BSrpnHCZ2pgtiMNnhjnsWJ2TMqq8
I routinely ignore posters with paid advertising in their sigs.  You should too.
instagibbs
Member
**
Offline Offline

Activity: 114
Merit: 12


View Profile
March 13, 2014, 08:14:16 PM
Last edit: March 13, 2014, 08:39:02 PM by instagibbs
 #14

Make a couple of bogus privkeys, use bitcoind to generate the 2-of-2 address, and use your script to generate it.  Post the keys, the redeemscripts (both of them) and the two resulting p2sh addresses.

Pubkey1:
0287f9169e265380a87cfd717ec543683f572db8b5a6d06231ff59c43429063ae4
Pubkey2:
0343947d178f20b8267488e488442650c27e1e9956c824077f646d6ce13a285d84

Bitcoind's answer:
"address" : "36m2bHwsUnkpGsZgVAEmeJRf2CeViDm6RV",
"redeemScript" : "52210287f9169e265380a87cfd717ec543683f572db8b5a6d06231ff59c43429063ae4210343947 d178f20b8267488e488442650c27e1e9956c824077f646d6ce13a285d8452ae"

My computation:
"address" : "35vpjKamX1LnkgjZ2aauj1XQWPRCkqRjsE",
"redeemScript"   "52210287f9169e265380a87cfd717ec543683f572db8b5a6d06231ff59c43429063ae4210343947 d178f20b8267488e488442650c27e1e9956c824077f646d6ce13a285d8452ae"


Nevermind I got it. I didn't understand that "2" actually means "0x52" in opcodes, etc. Piotr much love to you for your explanation.
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!