Here is a (mostly complete) example of creating and signing a raw transaction, it is only part of a larger project that takes requests from elsewhere but it should be enough to see the transaction handling code. Not exactly commented but hopefully the inputs are named well enough that you can tell what they are supposed to be, if not just ask.
And the usual disclaimer to check any generated transactions very carefully before letting them loose on the main network. It is also very possible that there are some calls in here that are too long to work with large amounts on a 32 bit system, I know I've replaced some calls in other scripts but not sure this one has ever been run except on 64-bit systems so make sure your test cases include large amounts at some point.
#!/usr/bin/perl
use Digest::SHA;
require("functions.inc");
$req=shift(@ARGV);
@r=split(/,/,$req);foreach $r(@r){@rr=split(/:/,$r,2);$req{$rr[0]}=$rr[1];}
$total=0;$onum=1;$sout='';undef %out;
@o=split(/-/,$req{'output'});foreach $o(@o){@oo=split(/=/,$o,2);
($net,$key)=decode_b58($oo[0]);
if($oo[1]>0 && $net==0 && $key ne ''){$out{$oo[0]}+=$oo[1];$total+=$oo[1];$onum++;
$a=sprintf('%x',$oo[1]);while(length($a)<16){$a="0".$a;}
$a=reverse($a);$sout.=pack('h*',$a);
$ss=pack('H*','76a914'.$key.'88ac');
$a=sprintf('%x',length($ss));if(length($a)<2){$a="0".$a;}
$sout.=pack('H*',$a).$ss;
}}
$have=0;$inum=0;
@o=split(/-/,$req{'input'});foreach $o(@o){($id,$cc,$amt,$addr)=split(/:/,$o,4);
$in{"$id:$cc"}=$addr;$have+=$amt;$inum++;
}
$fee=fee($onum,$inum);
if($total+$fee>$have){die("Insufficient Funds");}
$change=$have-$total-$fee;
if($change>0){
$out{$req{'change'}}=$change;$total+=$change;
$a=sprintf('%x',$change);while(length($a)<16){$a="0".$a;}
$a=reverse($a);$sout.=pack('h*',$a);
($net,$key)=decode_b58($req{'change'});
$ss=pack('H*','76a914'.$key.'88ac');
$a=sprintf('%x',length($ss));if(length($a)<2){$a="0".$a;}
$sout.=pack('H*',$a).$ss;
}
$send=sprintf('%x',$onum);if($onum<16){$send="0".$send;}
$send=pack('H*',$send).$sout."\x00\x00\x00\x00";
$start=sprintf('%x',1);while(length($start)<8){$start="0".$start;}
$start=reverse($start);$start=pack('h*',$start);
$c=sprintf('%x',$inum);if($inum<16){$c="0".$c;}
$start.=pack('H*',$c);
$xx=0;for my $x(keys %in){$a=$in{$x};$inlist.=$x.",";
($ptx,$oid)=split(/:/,$x);$idtx[$xx]=$x;
$ptx=reverse($ptx);$btx[$xx]=pack('h*',$ptx);
$c=sprintf('%x',$oid);while(length($c)<8){$c="0".$c;}
$c=reverse($c);$btx[$xx].=pack('h*',$c);
$btx[$xx].="\x00\xff\xff\xff\xff";
$xx++;
}chop($inlist);
$xx=0;while($xx<$inum){$tmp=$btx[$xx];
($ptx,$oid)=split(/:/,$idtx[$xx]);$a=$in{$idtx[$xx]};
$btx[$xx]=substr($btx[$xx],0,-5);
($net,$key)=decode_b58($a);$ss=pack('H*','76a914'.$key.'88ac');
$l=sprintf('%x',length($ss));if(length($l)<2){$l="0".$l;}
$btx[$xx].=pack('H*',$l).$ss."\xff\xff\xff\xff";
$pkey=addrprivkey($a);if($pkey eq ''){error("No private key found for $a");}
$tx=$start.join('',@btx).$send."\x01\x00\x00\x00";
$tx=Digest::SHA::sha256(Digest::SHA::sha256($tx));
$tx=unpack('H*',$tx);$ftx[$xx]=substr($tmp,0,-5);
$sig=signtx($tx,$pkey);
$l=sprintf('%x',length($sig)+1);if(length($l)<2){$l="0".$l;}
$sig=pack('H*',$l).$sig."\x01";
$pk=addrpubkey($a);$pk=pack('H*',$pk);
$l=sprintf('%x',length($pk));if(length($l)<2){$l="0".$l;}
$sig.=pack('H*',$l).$pk;
$l=sprintf('%x',length($sig));if(length($l)<2){$l="0".$l;}
$ftx[$xx].=pack('H*',$l).$sig."\xff\xff\xff\xff";
$btx[$xx]=$tmp;$xx++;
}
$dec=$start.join('',@ftx).$send;
$hex=unpack('H*',$dec);
$h=Digest::SHA::sha256(Digest::SHA::sha256($dec));
$hash=reverse(unpack('h*',$h));
print "Raw Transaction: $hex\nTransaction ID: $hash\n";
sub fee{my $o=shift(@_);my $i=shift(@_);my $f=0;
my $s=size($o,$i);my $pp=int($s/1024)+1;
if($req{'priority'}>=0){
$f+=10000*$pp;
}if($req{'priority'}>=1){
$f+=40000*$pp;
}if($req{'priority'}>=2){
$f+=$s;
}return $f;
}
sub size{my $o=shift(@_);my $i=shift(@_);
return 10+(180*$i)+(34*$o);
}
sub addrprivkey{my $a=shift(@_);
my $u=$db->prepare(qq{select privkey from address where address='$a'});
$u->execute();my $ep;if($ep=$u->fetchrow_array()){
return unpack('H*',$cipher->decrypt_hex($ep));
}
}
sub addrpubkey{my $a=shift(@_);
my $u=$db->prepare(qq{select pubkey from address where address='$a'});
$u->execute();my $ep=$u->fetchrow_array();return $ep;
}
And here is the functions.inc that it references:
use Digest::SHA qw(sha256 sha256_hex);
use Crypt::RIPEMD160;
use Math::BigInt lib => 'GMP';
@bb=(1..9,'A'..'H','J'..'N','P'..'Z','a'..'k','m'..'z');
sub decode_b58{my $x=shift(@_);
$x=~s/[0O]/o/g;$x=~s/[Il]/1/g;
my $ox=$x;my $n=Math::BigInt->new(0);
$i=Math::BigInt->new(0);while($x){my $c=chop($x);
my $cp=0;while($bb[$cp] ne $c && $cp<=$#bb){$cp++;}
$n+=$cp*(58**$i);$i++;
}$i=$n->as_hex();$i=substr($i,2);
$x=reverse($ox);my $c=chop($x);while($c eq '1'){$c=chop($x);$i="00".$i;}
if(length($i)%2==1){$i="0".$i;}
my $ver=substr($i,0,2);my $chk=substr($i,-8);
$i=substr($i,2,-8);
$n=pack('H*',$ver.$i);
my $h=Digest::SHA::sha256(Digest::SHA::sha256($n));
$c=substr(unpack('H*',$h),0,8);
$ver=hex($ver);
if($chk eq $c){return ($ver,$i);}
return ('','');
}
sub ecadd{my $a=shift(@_);my $b=shift(@_);
my $p=Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F');
my $ax=Math::BigInt->new('0x'.substr($a,2,64));
my $ay=Math::BigInt->new('0x'.substr($a,66));
my $bx=Math::BigInt->new('0x'.substr($b,2,64));
my $by=Math::BigInt->new('0x'.substr($b,66));
if($by eq ''){return $a;}
if($ay eq ''){return $b;}
if($ax==$bx){
if(($ay+$by)%$p==0){
return '';
}else{
my ($rx,$ry)=ecdouble($ax,$ay);
return('04'.substr($rx->as_hex(),2).substr($ry->as_hex(),2));
}
}
my $x=$bx-$ax;$x->bmodinv($p);
my $xx=(($by-$ay)*$x)%$p;
my $rx=($xx*$xx-$ax-$bx)%$p;
my $ry=($xx*($ax-$rx)-$ay)%$p;
$rx=substr($rx->as_hex(),2);$ry=substr($ry->as_hex(),2);
while(length($rx)<64){$rx="0".$rx;}while(length($ry)<64){$ry="0".$ry;}
return "04".$rx.$ry;
}
sub ecaddnum{my $ax=shift(@_);my $ay=shift(@_);my $bx=shift(@_);my $by=shift(@_);
my $p=Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F');
if($by eq ''){return($ax,$ay);}
if($ay eq ''){return($bx,$by);}
if($ax==$bx){
if(($ay+$by)%$p==0){
return('','');
}else{
return ecdouble($ax,$ay);
}
}
my $x=$bx-$ax;$x->bmodinv($p);
my $xx=(($by-$ay)*$x)%$p;
my $rx=($xx*$xx-$ax-$bx)%$p;
my $ry=($xx*($ax-$rx)-$ay)%$p;
return($rx,$ry);
}
sub ecmult{my $pb=shift(@_);my $p=shift(@_);
my $x=Math::BigInt->new('0x'.substr($pb,2,64));
my $y=Math::BigInt->new('0x'.substr($pb,66));
my $n=Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');
if($p==0){return '';}
if($y eq ''){return '';}
$p=$p%$n;$pp=$p*3;
my $i=Math::BigInt->new('1');
while($i<=$pp){$i=$i*2;}$i=$i/4;
my $rx=$x->copy();my $ry=$y->copy();
while($i>1){
($rx,$ry)=ecdouble($rx,$ry);
my $e=$pp->copy();$e->band($i);my $ee=$p->copy();$ee->band($i);
if($e!=0 && $ee==0){($rx,$ry)=ecaddnum($rx,$ry,$x,$y);}
if($e==0 && $ee!=0){($rx,$ry)=ecaddnum($rx,$ry,$x,-$y);}
$i=$i/2;
}
$rx=substr($rx->as_hex(),2);$ry=substr($ry->as_hex(),2);
while(length($rx)<64){$rx="0".$rx;}while(length($ry)<64){$ry="0".$ry;}
return "04".$rx.$ry;
}
sub ecdouble{my $x=shift(@_);my $y=shift(@_);
my $p=Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F');
my $l=2*$y;$l->bmodinv($p);
$l=($l*3*$x*$x)%$p;
my $xx=($l*$l-2*$x)%$p;
my $yy=($l*($x-$xx)-$y)%$p;
return($xx,$yy);
}
sub signtx{my $m=shift(@_);my $k=shift(@_);
my $mm=Math::BigInt->new('0x'.$m);
my $pk=Math::BigInt->new('0x'.$k);
my $n=Math::BigInt->new('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');
my @chars=('a'..'f', 0..9);my $rr=join '',map {$chars[rand @chars]} 1..64;
my $r=Math::BigInt->new('0x'.$rr);
my $k=$r%$n;
my $p=ecmult('0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8',$k);
$r=Math::BigInt->new('0x'.substr($p,2,64));
my $s=$k->copy();$s->bmodinv($n);
$s=$s*($mm+($pk*$r)%$n)%$n;
$r=substr($r->as_hex(),2);$s=substr($s->as_hex(),2);
if(length($r)%2==1){$r="0".$r;}if(length($s)%2==1){$s="0".$s;}
$k=substr($r,0,1);if($k>7 || ($k==0 && $k ne '0')){$r="00".$r;}
$k=substr($s,0,1);if($k>7 || ($k==0 && $k ne '0')){$s="00".$s;}
$r=pack('H*',$r);$s=pack('H*',$s);
my $lr=sprintf('%x',length($r));if(length($lr)<2){$lr="0".$lr;}
my $ls=sprintf('%x',length($s));if(length($ls)<2){$ls="0".$ls;}
$rs="\x02".pack('H*',$lr).$r."\x02".pack('H*',$ls).$s;
my $l=sprintf('%x',length($rs));if(length($l)<2){$l="0".$l;}
return("\x30".pack('H*',$l).$rs);
}
sub hashtoaddr{my $nb=shift(@_);my $x=shift(@_);
my $nbh=sprintf('%x',$nb);while(length($nbh)<2){$nbh="0".$nbh;}
$x=pack('H*',$nbh.$x);
my $addr=unpack('H*',$x);$x=Digest::SHA::sha256(Digest::SHA::sha256($x));
$addr.=substr(unpack('H*',$x),0,8);
my $out='';while(substr($addr,0,2) eq '00'){$out.="1";$addr=substr($addr,2);}
my $a=Math::BigInt->new('0x'.$addr);my $o='';
while($a>58){$o=$bb[$a%58].$o;$a=$a/58;}
$o=$bb[$a].$o;
return $out.$o;
}
sub pkeytoaddr{my $nb=shift(@_);my $x=shift(@_);
$x=pack('H*',$x);$x=Digest::SHA::sha256($x);
my $nbh=sprintf('%x',$nb);while(length($nbh)<2){$nbh="0".$nbh;}
$x=pack('H*',$nbh).Crypt::RIPEMD160->hash($x);
my $addr=unpack('H*',$x);$x=Digest::SHA::sha256(Digest::SHA::sha256($x));
$addr.=substr(unpack('H*',$x),0,8);
my $out='';while(substr($addr,0,2) eq '00'){$out.="1";$addr=substr($addr,2);}
my $a=Math::BigInt->new('0x'.$addr);my $o='';
while($a>58){$o=$bb[$a%58].$o;$a=$a/58;}
$o=$bb[$a].$o;
return $out.$o;
}
1;