Bitcoin Forum
November 18, 2018, 01:03:58 AM *
News: Latest Bitcoin Core release: 0.17.0 [Torrent].
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: A Kick Ass TransactionBuilder with Stealth Payment support !  (Read 3137 times)
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
October 27, 2014, 08:46:15 PM
 #1

I worked pretty hard this week to refactor how NBitcoin sign transactions.
Actually I got inspired by http://bitcore.io/blog/articles/transactionbuilder/.

Now you get a better, shinier, and C# version of that with Stealth Payment support and soon colored coin.

Quote
var stealthKeys = Enumerable.Range(0, 3).Select(_ => new Key()).ToArray();
var scanKey = new Key();

var satoshi = new BitcoinStealthAddress(scanKey.PubKey, stealthKeys.Select(k => k.PubKey).ToArray(), 2, new BitField(3, 5), Network.Main);

var bob = new Key();
var coins = new Coin[] {
            new Coin()
            {
               Outpoint = RandOutpoint(),
               TxOut = new TxOut("1.00",bob.PubKey.ID)
            } };

//Bob sends money to satoshi
TransactionBuilder builder = new TransactionBuilder();
var tx =
   builder
   .AddCoins(coins)
   .AddKeys(bob)
   .SendTo(satoshi, "1.00")
   .BuildTransaction(true);
Assert.True(builder.Verify(tx));

//Satoshi scans a StealthCoin in the transaction with his scan key
var stealthCoin = StealthCoin.Find(tx, satoshi, scanKey);
Assert.NotNull(stealthCoin);

//Satoshi sends back the money to Bob
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys)
      .AddKeys(scanKey)
      .SendTo(bob.PubKey.ID, "1.00")
      .BuildTransaction(true);

Assert.True(builder.Verify(tx)); //Signed !

         
//Same scenario, Satoshi wants to send money back to Bob
//However, his keys are spread on two machines
//He partially sign on the 1st machine
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys.Skip(2).ToArray()) //Only one Stealth Key
      .AddKeys(scanKey)
      .SendTo(bob.PubKey.ID, "1.00")
      .BuildTransaction(true);

Assert.False(builder.Verify(tx)); //Not fully signed

//Then he partially sign on the 2nd machine
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys[0]) //Other key
      .AddKeys(scanKey)
      .SignTransaction(tx);

Assert.True(builder.Verify(tx)); //Fully signed !

The process for other type of inputs are essentially the same.
The only difference is that for non-P2SH coins, you need to pass a Coin (outpoint,txout)
For a P2SH coin, you need to pass a ScriptCoin to the builder (outpoint,txout,amount)
For stealth, you pass a StealthCoin as above example.

You can chain other SendTo() if you have other non stealth payment to do.

TransactionBuilder allows you to mix multiple type of script. Given the right set of keys and coins, he will sign all what he can.
TransactionBuilder also select automatically the coins to cover the sum of money send with SendTo() + fees. (You can provide your own algorithm with interface ICoinSelector though)

Update : Article about it at http://www.codeproject.com/Articles/835098/NBitcoin-Build-Them-All

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction. Advertise here.
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
1542503038
Hero Member
*
Offline Offline

Posts: 1542503038

View Profile Personal Message (Offline)

Ignore
1542503038
Reply with quote  #2

1542503038
Report to moderator
unsoindovo
Legendary
*
Offline Offline

Activity: 1582
Merit: 1015


View Profile
October 30, 2014, 02:18:06 PM
 #2

I worked pretty hard this week to refactor how NBitcoin sign transactions.
Actually I got inspired by http://bitcore.io/blog/articles/transactionbuilder/.

Now you get a better, shinier, and C# version of that with Stealth Payment support and soon colored coin.

Quote
var stealthKeys = Enumerable.Range(0, 3).Select(_ => new Key()).ToArray();
var scanKey = new Key();

var satoshi = new BitcoinStealthAddress(scanKey.PubKey, stealthKeys.Select(k => k.PubKey).ToArray(), 2, new BitField(3, 5), Network.Main);

var bob = new Key();
var coins = new Coin[] {
            new Coin()
            {
               Outpoint = RandOutpoint(),
               TxOut = new TxOut("1.00",bob.PubKey.ID)
            } };

//Bob sends money to satoshi
TransactionBuilder builder = new TransactionBuilder();
var tx =
   builder
   .AddCoins(coins)
   .AddKeys(bob)
   .SendTo(satoshi, "1.00")
   .BuildTransaction(true);
Assert.True(builder.Verify(tx));

//Satoshi scans a StealthCoin in the transaction with his scan key
var stealthCoin = StealthCoin.Find(tx, satoshi, scanKey);
Assert.NotNull(stealthCoin);

//Satoshi sends back the money to Bob
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys)
      .AddKeys(scanKey)
      .SendTo(bob.PubKey.ID, "1.00")
      .BuildTransaction(true);

Assert.True(builder.Verify(tx)); //Signed !

         
//Same scenario, Satoshi wants to send money back to Bob
//However, his keys are spread on two machines
//He partially sign on the 1st machine
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys.Skip(2).ToArray()) //Only one Stealth Key
      .AddKeys(scanKey)
      .SendTo(bob.PubKey.ID, "1.00")
      .BuildTransaction(true);

Assert.False(builder.Verify(tx)); //Not fully signed

//Then he partially sign on the 2nd machine
builder = new TransactionBuilder();
tx =
   builder
      .AddCoins(stealthCoin)
      .AddKeys(stealthKeys[0]) //Other key
      .AddKeys(scanKey)
      .SignTransaction(tx);

Assert.True(builder.Verify(tx)); //Fully signed !

The process for other type of inputs are essentially the same.
The only difference is that for non-P2SH coins, you need to pass a Coin (outpoint,txout)
For a P2SH coin, you need to pass a ScriptCoin to the builder (outpoint,txout,amount)
For stealth, you pass a StealthCoin as above example.

You can chain other SendTo() if you have other non stealth payment to do.

TransactionBuilder allows you to mix multiple type of script. Given the right set of keys and coins, he will sign all what he can.
TransactionBuilder also select automatically the coins to cover the sum of money send with SendTo() + fees. (You can provide your own algorithm with interface ICoinSelector though)

this is really interesting...
it is ok to use this library version?

https://github.com/NicolasDorier/NBitcoin

thanks
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
October 30, 2014, 02:37:32 PM
 #3

Yes, it is ok, but wait until tonight, I will release an article on Codeproject where I show off all the following scenarios, in an easy to run console application.

P2PK, P2PKH, Multi Sig payment
-Alice sends to Satoshi
-Alice then Bob sends to Satoshi
-AliceBobNico corp. sends to Satoshi
P2SH payment
-AliceBobNico corp. sends to Satoshi
-Stealth Payment
-DarkSatoshi sends money to DarkBob and Alice
Colored coin Payment
-GoldGuy emits gold to Nico and Satoshi, SilverGuy emits silver to Alice
-Nico sends gold to Satoshi.
-Satoshi and Alice wants to swap gold against silver and BTC.
The Transaction of the Hell (only for warriors)
-GoldGuy and SilverGuy agrees to emit Platinium to Satoshi.
 Satoshi wants to trade Platinium against the Silver of Alice, the Gold of Nico, BTC from DarkBob and BTC from AliceBobNico corp.

Colored Coin support is done !
I keep you informed Smiley

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
October 30, 2014, 08:15:02 PM
 #4

Here you go !

http://www.codeproject.com/Articles/835098/NBitcoin-Build-Them-All Smiley

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
dabura667
Sr. Member
****
Offline Offline

Activity: 478
Merit: 250


View Profile
November 01, 2014, 10:34:13 AM
 #5

Does it support p2sh stealth addresses with prefixes?

I have a hand-made stealth that I've been using.

My Tip Address:
1DXcHTJS2DJ3xDoxw22wCt11FeAsgfzdBU
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
November 01, 2014, 03:39:46 PM
 #6

Does it support p2sh stealth addresses with prefixes?

I have a hand-made stealth that I've been using.

Prefixes are implemented yes.
However p2sh on stealth payment is not something I knew about.
Is there something in the stealth address format that got added to specify payment should be made p2sh ? or is it up to the payer to decide that ?

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
November 02, 2014, 03:16:59 AM
 #7

Does it support p2sh stealth addresses with prefixes?

I have a hand-made stealth that I've been using.

I've committed support for P2SH stealth addresses today.
Since the knowledge of whether a payment should be made P2SH is not in the stealth address format (btw, why not using the Option byte for encoding that info ?), the payer should specify it when sending money.

Quote
TransactionBuilder.Send(BitcoinStealthAddress address,Money amount, bool p2sh = false)

The scanner side (The StealthCoin.Find method in my article) is unchanged and will automatically detect whether this is a P2SH stealth payment or not, and sign the spending correctly in both case.

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
November 02, 2014, 05:08:39 PM
 #8

Also, I have a second question relating to P2SH Stealth Address.
If one receive a P2SH Stealth Payment with prefix matching, then he needs to know if the P2SH scriptPubKey is correct.

The problem is, for that to happen, all the keys should be uncovered by the scanner, which is a costly.
If no P2SH is used, only 1 uncover operation is enough.

So, a scanner supporting P2SH stealth payment is potentially weaker to DDOS attack. right ?

This make me think that no scanner should assume P2SH stealth payment is possible by default, except if specified otherwise.

Since the scanner knows the Stealth Address, it would be nice to include this information in the Option byte.
So, the payment receiver can decide himself, at the time of the address creation, if he accepts the risk or not.
And better, the payer don't have to ask the question out of band whether he uses P2SH or not.

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
dabura667
Sr. Member
****
Offline Offline

Activity: 478
Merit: 250


View Profile
November 03, 2014, 09:50:45 AM
 #9

Also, I have a second question relating to P2SH Stealth Address.
If one receive a P2SH Stealth Payment with prefix matching, then he needs to know if the P2SH scriptPubKey is correct.

The problem is, for that to happen, all the keys should be uncovered by the scanner, which is a costly.
If no P2SH is used, only 1 uncover operation is enough.

So, a scanner supporting P2SH stealth payment is potentially weaker to DDOS attack. right ?

This make me think that no scanner should assume P2SH stealth payment is possible by default, except if specified otherwise.

Since the scanner knows the Stealth Address, it would be nice to include this information in the Option byte.
So, the payment receiver can decide himself, at the time of the address creation, if he accepts the risk or not.
And better, the payer don't have to ask the question out of band whether he uses P2SH or not.

https://wiki.unsystem.net/en/index.php/DarkWallet/Stealth#Address_format
Stealth addresses have a byte for N and a byte for M (Shown in wiki as [number_sigs:1]) in the "M-of-N" (aka multisig)

If N = 0x01, then p2pkh If N > 0x01, then p2sh.

As for operations, you will have to discover the shared secret once (using scan_pubkey only), then just add the shared secret point to each spend pubkey. So the difference between the calculations for a p2pkh stealth and a p2sh stealth is + (N - 1) EC additions (which are trivial, EC multiply is where the real heavy lifting takes place) so it's negligible.

Let me show some example code:

Code:
def find_stealth_address(scan_pubkey, spend_pubkeys, ephemkey, multisig_m = 0):
##### This takes scan_pubkey and spend_pubkeys and creates the new address
    S1 = diffie_hellman(ephemkey.secret, scan_pubkey)
    ##### Scan pubkey * ephem_secret (this function is for generating the address for sending.)
    c = sha256(S1)
    ##### Hashing the compressed pubkey shared secret once with sha256 to get an integer.
    shared_point = EC_KEY(c).pubkey.point
    ##### Generate the point from the shared secret and get the point (pubkey point)
    addr_pubkey = []
    ##### initialize address pubkey list
    if len(spend_pubkeys) == 1:
    ##### If Length of spend pubkeys (should be same as N, its sanity is checked in separate function.)
        point = ser_to_point(spend_pubkeys[0]) + shared_point
        ##### Add the spend pubkey point to the shared point
        addr_pubkey.append(point_to_ser(point))
        ##### Add compressed pubkey to addr pubkey list
        address = public_key_to_bc_address(addr_pubkey[0])
        ##### Get address
    else:
    ##### When N > 1
        assert multisig_m > 0 and multisig_m <= len(spend_pubkeys)
        ##### sanity check, M should be at least 1, M should be less or equal to N.
        for i in range(len(spend_pubkeys)):
            point = ser_to_point(spend_pubkeys[i]) + shared_point
            ##### Add each of N spend pubkeys to the same shared secret point
            addr_pubkey.append(point_to_ser(point).encode('hex'))
            ##### Add the pubkeys to the addr pubkeys list
        from transaction import Transaction
        redeem_script = Transaction.multisig_script(sorted(addr_pubkey), multisig_m)
        ##### Generate redeemscript by sorting the addr_pubkeys first (canonical)
        address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
        ##### Generate the p2sh address
    return address

The calculations for the p2sh are not much more, just extra point additions.

My Tip Address:
1DXcHTJS2DJ3xDoxw22wCt11FeAsgfzdBU
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
November 03, 2014, 12:39:27 PM
 #10

Also, I have a second question relating to P2SH Stealth Address.
If one receive a P2SH Stealth Payment with prefix matching, then he needs to know if the P2SH scriptPubKey is correct.

The problem is, for that to happen, all the keys should be uncovered by the scanner, which is a costly.
If no P2SH is used, only 1 uncover operation is enough.

So, a scanner supporting P2SH stealth payment is potentially weaker to DDOS attack. right ?

This make me think that no scanner should assume P2SH stealth payment is possible by default, except if specified otherwise.

Since the scanner knows the Stealth Address, it would be nice to include this information in the Option byte.
So, the payment receiver can decide himself, at the time of the address creation, if he accepts the risk or not.
And better, the payer don't have to ask the question out of band whether he uses P2SH or not.

https://wiki.unsystem.net/en/index.php/DarkWallet/Stealth#Address_format
Stealth addresses have a byte for N and a byte for M (Shown in wiki as [number_sigs:1]) in the "M-of-N" (aka multisig)

If N = 0x01, then p2pkh If N > 0x01, then p2sh.

As for operations, you will have to discover the shared secret once (using scan_pubkey only), then just add the shared secret point to each spend pubkey. So the difference between the calculations for a p2pkh stealth and a p2sh stealth is + (N - 1) EC additions (which are trivial, EC multiply is where the real heavy lifting takes place) so it's negligible.

Let me show some example code:

Code:
def find_stealth_address(scan_pubkey, spend_pubkeys, ephemkey, multisig_m = 0):
##### This takes scan_pubkey and spend_pubkeys and creates the new address
    S1 = diffie_hellman(ephemkey.secret, scan_pubkey)
    ##### Scan pubkey * ephem_secret (this function is for generating the address for sending.)
    c = sha256(S1)
    ##### Hashing the compressed pubkey shared secret once with sha256 to get an integer.
    shared_point = EC_KEY(c).pubkey.point
    ##### Generate the point from the shared secret and get the point (pubkey point)
    addr_pubkey = []
    ##### initialize address pubkey list
    if len(spend_pubkeys) == 1:
    ##### If Length of spend pubkeys (should be same as N, its sanity is checked in separate function.)
        point = ser_to_point(spend_pubkeys[0]) + shared_point
        ##### Add the spend pubkey point to the shared point
        addr_pubkey.append(point_to_ser(point))
        ##### Add compressed pubkey to addr pubkey list
        address = public_key_to_bc_address(addr_pubkey[0])
        ##### Get address
    else:
    ##### When N > 1
        assert multisig_m > 0 and multisig_m <= len(spend_pubkeys)
        ##### sanity check, M should be at least 1, M should be less or equal to N.
        for i in range(len(spend_pubkeys)):
            point = ser_to_point(spend_pubkeys[i]) + shared_point
            ##### Add each of N spend pubkeys to the same shared secret point
            addr_pubkey.append(point_to_ser(point).encode('hex'))
            ##### Add the pubkeys to the addr pubkeys list
        from transaction import Transaction
        redeem_script = Transaction.multisig_script(sorted(addr_pubkey), multisig_m)
        ##### Generate redeemscript by sorting the addr_pubkeys first (canonical)
        address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
        ##### Generate the p2sh address
    return address

The calculations for the p2sh are not much more, just extra point additions.

Shit always thought it was native multi sig. Well so I'll change that.

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 700
Merit: 501


View Profile
November 03, 2014, 01:26:02 PM
 #11

Fixed that, now not using native multi sig, but p2sh.
You can run sample at http://www.codeproject.com/Articles/835098/NBitcoin-Build-Them-All after make a Update-Package NBitcoin to play with.
Or run the CanBuildStealthTransaction test in NBitcoin.

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
hitjariwala
Newbie
*
Offline Offline

Activity: 15
Merit: 0


View Profile
January 23, 2017, 09:56:50 AM
 #12

Hi,

I try this code and return success transaction but bitcoin not transfer to address.

Please help.
Here my code.

var network = alice.Network;
       
       
        Transaction aliceFunding = new Transaction()
        {
            Outputs =
                {
                    new TxOut("0.0009", alice.PrivateKey.PubKey)
                }
        };
        Coin[] aliceCoins = aliceFunding
                                .Outputs
                                .Select((o, i) => new Coin(new OutPoint(aliceFunding.GetHash(), i), o))
                                .ToArray();

        //Now Alice wants to send 1.00 BTC to Satoshi, with 0.001 BTC fees for miners.
        var txBuilder = new TransactionBuilder();
        var tx = txBuilder
            .AddCoins(aliceCoins)
            .AddKeys(alice.PrivateKey)
            .Send(satoshi.GetAddress(), "0.0002")
            .SendFees("0.0001")
            .SetChange(alice.GetAddress())
            .BuildTransaction(true);

        txRepo.Put(tx.GetHash(), tx);
        //Assert(txBuilder.Verify(tx)); //check fully signed
        Literal1.Text = tx.GetHash().ToString();

        var client = new QBitNinjaClient(network);
        BroadcastResponse broadcastResponse = client.Broadcast(tx).Result;


        if (!broadcastResponse.Success)
        {
            Literal1.Text = (string.Format("ErrorCode: {0}", broadcastResponse.Error.ErrorCode)).ToString();
            //Literal2.Text = ("Error message: " + broadcastResponse.Error.Reason).ToString();
        }
        else
        {
            Literal1.Text = ("Success! You can check out the hash of the transaciton in any block explorer:").ToString();
            //Literal2.Text = (transaction.GetHash()).ToString();
        }
Pages: [1]
  Print  
 
Jump to:  

Sponsored by , a Bitcoin-accepting VPN.
Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!