Skip to content
This repository has been archived by the owner on Dec 28, 2023. It is now read-only.

Public Transaction

Gary Yu edited this page Aug 13, 2018 · 10 revisions

As discussed in Commitment w/o Range Proof, a public transaction can be defined in this form:

(ri*G+0*H) + excess'+ (offset*G+(vi-vor-voc-fee)*H) = (roc*G+0*H) + (ror*G+0*H)

And this one doesn't have any practical meaning, since all the inputs and outputs are just public key (i.e. address like Bitcoin). And since all the values are transparently public, the separated excess' and offset are no more useful and can be merged. Let's re-design above equation:

ri*G + excess = roc*G + ror*G
vi = vor + voc + fee

We still keep excess here for the purpose of randomly selecting roc for change output (or change address).

Instead of Schnorr Shared Signature, we just adopt single signature from the sender, to verify input ri*G belongs to this sender. The sum ri*G + excess is used for signature public key, and excess is randomly selected by sender.

But, wait! Will this be a disaster on that wonderful cut-through feature of Mimblewimble protocol ?

Yes. Once cut-through happen, the spent output commitment and its related input commitment will be removed, then, a signature with a input commitment as the public key can't verify signature any more!

So, to keep the wonderful cut-through feature, the signature must be bound to output commitment instead of input commitment, since UTXOs never cut-through.

Let's try to give a new design for public transaction, more simple than design in previous page.

1. New Design of Public Transaction Implementation

ri*G + excess = roc*G + ror*G
vi = vor + voc + fee

Design:

  • Let xS and xR are private keys of sender and receiver, which are used for Schnorr Signature.
  • xG = xSG+xRG is used as the final signature's public key, this is a typical shared signature.
  • xS comes from calculation: xS = roc - ri, where roc is sender selected random secret for the change output.
  • xR is the blinding factor of receiver output.
  • The receiver need check the balance by change_output + receiver_output = input + excess and vi-vor-voc-fee=0, where excess = xRG + xSG.
  • kR and kS are secret nonces of receiver and sender. nonce_sum = kRG + kSG is used for aggregated Schnorr Signature.
  • The sign message is fee || lock_height.
  • Put excess (i.e. xG) as the public key of the signature in transaction kernel and publish.
Source Code (Click to expand)

    #[test]
    #[allow(non_snake_case)]
    fn simple_public_transaction() {

        fn commit(value: u64, blinding: SecretKey) -> Commitment {
            let secp = Secp256k1::with_caps(ContextFlag::Commit);
            secp.commit(value, blinding).unwrap()
        }

        println!("\nPT Mutual Procedure: Round 1 (on sender side)");

        let sender_sk; // private key of sender
        let sender_kS; // secretnonce of sender

        let (input,change_output,fee,in_amount, out_amount,change_amount, lock_height,kSG,xSG) = {

            let secp = Secp256k1::with_caps(ContextFlag::Full);

            let in_amount:  u64 = 10 * 1_000_000_000;
            let out_amount: u64 =  8 * 1_000_000_000;

            //--- step 2. Set lock_height for transaction kernel (current chain height)
            let lock_height: u64 = 10_000;   // just for example

            //--- step 3. Select inputs using desired selection strategy
            //simulate an UTXO as the input
            let blinding_input = SecretKey::new(&secp, &mut OsRng::new().unwrap());
            let input = commit(0, blinding_input);

            //--- step 7. Skipped.
            //--- step 8. Calculate fee: tx_weight * 1_000_000 nG
            let fee: u64 = 8 * 1_000_000;

            //--- step 4. Create change_output
            //--- step 5. Select blinding factor for change_output
            let blinding_change_output = SecretKey::new(&secp, &mut OsRng::new().unwrap());
            let change_output = commit(0, blinding_change_output);

            //--- step 9. Calculate total blinding excess sum xS (private scalar), for all inputs(-) and outputs(+)
            let xS = secp.blind_sum(vec![blinding_change_output], vec![blinding_input]).unwrap();

            //--- step 10. Select a random nonce kS (private scalar)
            let kS = SecretKey::new(&secp, &mut OsRng::new().unwrap());

            sender_sk = xS; // save for final round
            sender_kS = kS; // save for final round

            //--- step 12. Multiply xS and kS by generator G to create public curve points xSG and kSG
            let xSG = PublicKey::from_secret_key(&secp, &xS).unwrap();
            let kSG = PublicKey::from_secret_key(&secp, &kS).unwrap();

            //--- step 13. Add values to Slate for passing to other participants: UUID, inputs, change_outputs, fee, amount, lock_height, kSG, xSG, oS
            (input,change_output,fee,in_amount,out_amount,in_amount-out_amount-fee,lock_height,kSG,xSG)
        };

        println!("\nPT Mutual Procedure: Round 1 Done. Sender post to Receiver: inputs, change_outputs, fee, amount, lock_height, kSG, xSG");

        println!("\nPT Mutual Procedure: Round 2 (on receiver side)");

        let (sR, xRG, kRG, receiver_output) = {
            let secp = Secp256k1::with_caps(ContextFlag::Full);

            //--- step 1. Check fee against number of inputs, change_outputs +1 * receiver_output)
            //skipped.

            //--- step 2. Create receiver_output
            //--- step 3. Choose random blinding factor for receiver_output xR (private scalar)
            let xR = SecretKey::new(&secp, &mut OsRng::new().unwrap());
            let output = commit(0, xR);

            //--- step 4. Calculate message M = fee | lock_height
            let msg = Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();

            //--- step 5. Choose random nonce kR (private scalar)
            let kR = SecretKey::new(&secp, &mut OsRng::new().unwrap());

            //--- step 6. Multiply xR and kR by generator G to create public curve points xRG and kRG
            let xRG = PublicKey::from_secret_key(&secp, &xR).unwrap();
            let kRG = PublicKey::from_secret_key(&secp, &kR).unwrap();

            let xG = PublicKey::from_combination(&secp, vec![&xRG, &xSG]).unwrap();
            let excess = Commitment::from_pubkey(&secp, &xG).unwrap();
            let balance = in_amount-out_amount-change_amount-fee;
            if balance==0 && secp.verify_commit_sum(
                vec![output, change_output],
                vec![input,excess ]) {
                println!("\ntotal sum balance OK:\toutput + change_output = input + excess");
            }else{
                println!("\ntotal sum balance NOK:\toutput + change_output != input + excess");
            }

            //--- step 7. Compute Schnorr challenge e = SHA256(M | kRG + kSG)
            //--- step 8. Compute Recipient Schnorr signature sR = kR + e * xR
            let nonce_sum = PublicKey::from_combination(&secp, vec![&kRG, &kSG]).unwrap();
            let sR = sign_single(&secp, &msg, &xR, Some(&kR), Some(&nonce_sum), Some(&nonce_sum)).unwrap();

            //--- step 9. Add sR, xRG, kRG to Slate
            //--- step 10. Create wallet output function rF that stores receiver_output in wallet
            //             with status "Unconfirmed" and identifying transaction log entry TR linking
            //             receiver_output with transaction.

            (sR, xRG, kRG, output)
        };

        println!("\nPT Mutual Procedure: Round 2 Done. Receiver post to Sender: sR, xRG, kRG, receiver_output");

        println!("\nPT Mutual Procedure: Final Round (on sender side)");

        let (s, excess, fee, lock_height) = {
            let secp = Secp256k1::with_caps(ContextFlag::Full);

            //--- step 1. Calculate message M = fee | lock_height
            let msg = Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();

            //--- step 2. Compute Schnorr challenge e = SHA256(M | kRG + kSG)
            //--- step 3. Verify sR by verifying kRG = sRG - e * xRG
            let nonce_sum = PublicKey::from_combination(&secp, vec![&kRG, &kSG]).unwrap();
            let result = verify_single(&secp, &sR, &msg, Some(&nonce_sum), &xRG, true);
            if true==result {
                println!("Signature 'sR' Verification:\tOK");
            }else{
                println!("Signature 'sR' Verification:\tNOK");
            }

            //--- step 4. Compute Sender Schnorr signature sS = kS + e * xS
            let xS = sender_sk; // load sender's private key , which is saved in 1st round
            let kS = sender_kS; // load sender's secret nonce, which is saved in 1st round
            let sS = sign_single(&secp, &msg, &xS, Some(&kS), Some(&nonce_sum), Some(&nonce_sum)).unwrap();

            //--- step 5. Calculate final signature s = (sS+sR, kSG+kRG)
            let sig_vec = vec![&sR, &sS];
            let s = add_signatures_single(&secp, sig_vec, &nonce_sum).unwrap();

            //--- step 6. Calculate public key for s: xG = xRG + xSG
            let xG = PublicKey::from_combination(&secp, vec![&xRG, &xSG]).unwrap();
            let excess = Commitment::from_pubkey(&secp, &xG).unwrap();
            let balance = in_amount-out_amount-change_amount-fee;
            if balance==0 && secp.verify_commit_sum(
                vec![receiver_output, change_output],
                vec![input,excess ]) {
                println!("\ntotal sum balance OK:\toutput + change_output = input + excess");
            }else{
                println!("\ntotal sum balance NOK:\toutput + change_output != input + excess");
            }

            //--- step 7. Verify s against excess values in final transaction using xG
            let result = verify_single(&secp, &s, &msg, Some(&nonce_sum), &xG, false);
            if true==result {
                println!("Signature 's' Verification:\tOK");
            }else{
                println!("Signature 's' Verification:\tNOK");
            }

            //--- step 8. Create Transaction Kernel Containing:
            //            Signature: s, Public key: excess, fee, lock_height
            (s, excess, fee, lock_height)
        };

        println!("\nPT Mutual Procedure: Final Round Done. Sender post to mempool: s, excess, fee, lock_height, and input,outputs");

        println!("\ns:\t\t{:?}\nexcess:\t{:?}\nfee:\t\t{:?}\nlock_height:\t{:?}\n",
                 s,
                 excess,
                 fee, lock_height);

        println!("\nInput Commit:\t{:?}\nValue:\t{:?}", input, in_amount);
        println!("\nReceiver Output Commit:\t{:?}\nValue:\t{:?}", receiver_output, out_amount);
        println!("\nChange Output Commit:\t{:?}\nValue:\t{:?}", change_output, change_amount);
    }

And here is the running result:

$ cargo test --release simple_public_transaction -- --nocapture


running 1 test

PT Mutual Procedure: Round 1 (on sender side)

PT Mutual Procedure: Round 1 Done. Sender post to Receiver: inputs, change_outputs, fee, amount, lock_height, kSG, xSG

PT Mutual Procedure: Round 2 (on receiver side)

total sum balance OK:	output + change_output = input + excess

PT Mutual Procedure: Round 2 Done. Receiver post to Sender: sR, xRG, kRG, receiver_output

PT Mutual Procedure: Final Round (on sender side)
Signature 'sR' Verification:	OK

total sum balance OK:	output + change_output = input + excess
Signature 's' Verification:	OK

PT Mutual Procedure: Final Round Done. Sender post to mempool: s, excess, fee, lock_height, and input,outputs

s:		Signature(d0a789df412cd2f912723330101f97315b71027b04fd374cbb35f580f4b1b7542058dc5f0d43c2c0c51c1378ee0f13f433c164fe592434ac172bc17feedc23ac)
excess:	Commitment(09ee57046b9b6cc869cc2f4478a75e581fed1b771620d57590fc1f8aafa94b5f31)
fee:		8000000
lock_height:	10000


Input Commit:	Commitment(093cf16cf4becb4a3a700a89a18d4055497a51965b6cc0c793e222b2fa8c33f169)
Value:	10000000000

Receiver Output Commit:	Commitment(09db193924f93e752697a704424b7591d6e107f71c2978e38c43c1e28c731982c0)
Value:	8000000000

Change Output Commit:	Commitment(0809944de5aca98226e2edc863075e49dcd2a66e0046e513f5e55ce3614f3c426e)
Value:	1992000000

(To Be Continued)