// convert unsigned 8-bit integer to hex string
function hex8($in)
{
return str_pad(dechex($in),2,"0",STR_PAD_LEFT);
}
// convert signed 32-bit integer to little-endian hex string
function hex32($in)
{
return hex8($in&0xFF).hex8($in>>8&0xFF).hex8($in>>16&0xFF).hex8($in>>24&0xFF);
}
// convert string representation of an unsigned 64-bit integer to
// little-endian hex string
function hex64($in)
{
$msw=gmp_div(gmp_init($in),gmp_init("4294967296"));
$lsw=gmp_sub(gmp_init($in),gmp_mul($msw,gmp_init("4294967296")));
if (gmp_cmp($lsw,gmp_init("2147483647"))>0)
$lsw=gmp_sub($lsw,gmp_init("4294967296"));
$msw_i=intval(gmp_strval($msw));
$lsw_i=intval(gmp_strval($lsw));
return hex32($lsw_i).hex32($msw_i);
}
// convert string representation of fixed-point Bitcoin quantity to
// string representation of 64-bit integer satoshis
function btctosatoshi($n)
{
$np=explode(".",$n);
if (count($np)==2)
{
$whole=$np[0];
$frac=substr($np[1],0,8);
}
else
{
if (strpos(".",$n)>0)
{
$whole="0";
$frac=substr($np[0],0,8);
}
else
{
$whole=$np[0];
$frac="0";
}
}
if ($whole=="")
$whole="0";
if ($frac=="")
$frac="0";
return ltrim($whole.str_pad($frac,8,"0"),"0");
}
// get hash160 of a Bitcoin address
function addrtohash($addr)
{
$code_string = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
$num=gmp_init(0);
for ($i=0; $i<strlen($addr); $i++)
$num=gmp_add(gmp_mul($num, 58), strpos($code_string, substr($addr, $i, 1)));
$hex=gmp_strval($num, 16);
return substr($hex, strlen($hex)-48, 40);
}
// reverse input string in groups of two characters
function reverse($in)
{
$out="";
for ($i=0; $i<strlen($in); $i+=2)
$out=substr($in,$i,2).$out;
return $out;
}
echo hex32(1); // version
$p=1; // argument position
$comment=$argv[$p++]; // comment to attach to first tx_out
$txin_qty=$argv[$p++]; // number of inputs
echo hex8($txin_qty);
for ($i=0; $i<$txin_qty; $i++)
{
echo reverse($argv[$p++]); // input tx hash
echo hex32($argv[$p++]); // input tx index
echo "00"; // sigscript size
echo "ffffffff"; // filler
}
$txout_qty=$argv[$p++]; // number of outputs
echo hex8($txout_qty+((strlen($comment)>0)?1:0));
for ($i=0; $i<$txout_qty+((strlen($comment)>0)?1:0); $i++)
{
if ($i!=$txout_qty)
{
echo hex64(btctosatoshi($argv[$p++])); // amount, in satoshis
echo hex8(25); // script length
echo "76"; // OP_DUP
echo "a9"; // OP_HASH160
echo "14"; // hash length
echo addrtohash($argv[$p++]); // hash
echo "88"; // OP_EQUALVERIFY
echo "ac"; // OP_CHECKSIG
}
else
{
echo hex64(0);
echo hex64(0);
echo hex64(0);
echo hex64(0);
echo hex32(-1);
echo hex8(3+strlen($comment));
echo "4c"; // OP_PUSHDATA1
echo hex8(strlen($comment)); // comment length
for ($j=0; $j<strlen($comment); $j++)
echo hex8(ord(substr($comment, $j, 1)));
echo "75"; // OP_DROP
}
}
echo "00000000\n"; // more filler
?>
As input, it takes a comment (use "" for no comment), one or more unused previous-transaction outputs as sources (txid-index pair), and one or more new outputs (value-address pair). Here's a possible mainnet invocation that combines 8 unused inputs at 1JL83axd56CQp9NA6k8VkZL526NBhUsapN totaling .0007944 BTC, sends .0001 BTC to 177fNKjH6kkZ8nLVcZWDnLC5Vz9hkjJYAp, returns .0001944 BTC to 1JL83axd56CQp9NA6k8VkZL526NBhUsapN, and leaves .0005 BTC as a fee:
8 \
e450dc7394f8432d89c68d0df6a5506cb81eac60c977bfc7932633aaf835a31f 1201 \
8867f0f84e80588ed00a05e604487442546ef42ab4c93445a09b00a6d487e74b 294 \
be07132738abff8793a7fa53dc47089a78d223267d0f20f635e33292dd1c0ba2 1125 \
1b2fa6c3d6036aead0a0c011738123ec97e0c83e7d1f077ee6db354a6471773a 112 \
df8ec32ad591396e36e4721909d3e747fb1091f3ee9d15b14dc3250e28695d44 236 \
8397db0c620d2fe6a05a63b791eebdda5737bf2a684c2dab4666c47feced58c2 1026 \
881c603656801e599d96006ef7e54450bb39f4bba7db8984e5562bc2883db938 102 \
f79d17445c63371c0b57053fcb324fd5f0c09143f34582fbcf39ce737c1bdada 39
2 \
.0001944 1JL83axd56CQp9NA6k8VkZL526NBhUsapN \
.0001 177fNKjH6kkZ8nLVcZWDnLC5Vz9hkjJYAp
It produces this output:
Feed this to bitcoind decoderawtransaction, and you get valid output:
"txid" : "e657b8db99066bda49955a41257d924f1eded506874c9733e6b532b71a8a591b",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "e450dc7394f8432d89c68d0df6a5506cb81eac60c977bfc7932633aaf835a31f",
"vout" : 1201,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "8867f0f84e80588ed00a05e604487442546ef42ab4c93445a09b00a6d487e74b",
"vout" : 294,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "be07132738abff8793a7fa53dc47089a78d223267d0f20f635e33292dd1c0ba2",
"vout" : 1125,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "1b2fa6c3d6036aead0a0c011738123ec97e0c83e7d1f077ee6db354a6471773a",
"vout" : 112,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "df8ec32ad591396e36e4721909d3e747fb1091f3ee9d15b14dc3250e28695d44",
"vout" : 236,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "8397db0c620d2fe6a05a63b791eebdda5737bf2a684c2dab4666c47feced58c2",
"vout" : 1026,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "881c603656801e599d96006ef7e54450bb39f4bba7db8984e5562bc2883db938",
"vout" : 102,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
},
{
"txid" : "f79d17445c63371c0b57053fcb324fd5f0c09143f34582fbcf39ce737c1bdada",
"vout" : 39,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.00019440,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 be17ec0fc1f8aa029223dbe5f53109d0faf8c797 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914be17ec0fc1f8aa029223dbe5f53109d0faf8c79788ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1JL83axd56CQp9NA6k8VkZL526NBhUsapN"
]
}
},
{
"value" : 0.00010000,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 43134735b72a1e9cf5e4c56d9102953132352ba6 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a91443134735b72a1e9cf5e4c56d9102953132352ba688ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"177fNKjH6kkZ8nLVcZWDnLC5Vz9hkjJYAp"
]
}
},
{
"value" : 0.00000000,
"n" : 2,
"scriptPubKey" : {
"asm" : "",
"hex" : "",
"type" : "nonstandard"
}
}
]
}
The last bit is a zero-value output that pushes the string "This is a comment attached to a transaction on mainnet." onto the stack, and then pulls it off:
Looking at the illustration above, they're an "arbitrary data" block after a couple of standard TxOuts.
Try signing it (yes, the private key for 1JL83axd56CQp9NA6k8VkZL526NBhUsapN is in my wallet), though, and you get this:
Leave the comment out, though, and the transaction can be signed and sent. I had also experimented with including the comment with one of the existing TxOuts...they went through OK on testnet, but bitcoind considered them "strange" and doesn't want to include them in wallet totals.
It seems like one or both of these should work, so why aren't they? Am I missing something?