NEO Views UTXO Transactions from Source Code Analysis

Keywords: network github Blockchain Linux

0x00 Preface

Community Big Brother: "Trading is the only way to operate the block chain."

0x01 transaction type

In NEO, almost all operations on block chains except consensus are a kind of "transaction", and even in the face of "transaction", contracts are only a younger brother. The transaction type is defined in Transaction Type in Core:

Source location: neo/Core/TransactionType

        /// <summary>
        /// Special transactions for allocating byte charges
        /// </summary>
        [ReflectionCache(typeof(MinerTransaction))]
        MinerTransaction = 0x00,

        /// <summary>
        /// Special transactions for asset distribution
        /// </summary>
        [ReflectionCache(typeof(IssueTransaction))]
        IssueTransaction = 0x01,
        
        [ReflectionCache(typeof(ClaimTransaction))]
        ClaimTransaction = 0x02,

        /// <summary>
        /// Special transactions for enrolling as bookkeeping candidates
        /// </summary>
        [ReflectionCache(typeof(EnrollmentTransaction))]
        EnrollmentTransaction = 0x20,

        /// <summary>
        /// Special transactions for asset registration
        /// </summary>
        [ReflectionCache(typeof(RegisterTransaction))]
        RegisterTransaction = 0x40,

        /// <summary>
        /// Contract trading, which is one of the most commonly used transactions
        /// </summary>
        [ReflectionCache(typeof(ContractTransaction))]
        ContractTransaction = 0x80,

        /// <summary>
        /// Voting contract//voting Dialog
        /// </summary>
        [ReflectionCache(typeof(StateTransaction))]
        StateTransaction = 0x90,
        /// <summary>
        /// Publish scripts to the blockchain for being invoked later.
        /// </summary>
        [ReflectionCache(typeof(PublishTransaction))]
        PublishTransaction = 0xd0,
        /// <summary>
        /// Call contract GUI invocation ransaction dialog
        /// </summary>
        [ReflectionCache(typeof(InvocationTransaction))]
        InvocationTransaction = 0xd1

These transactions not only have a variety of names, but also have some differences in actual functions and what I "think" about. In order to figure out what each transaction does, I almost turned over the source code of NEO and GUI.

  • Miner Transaction: The miner fee transaction, which was added by the Speaker when the new block was created. Use location: neo/Consensus/Consensus Service.cs/Create MinerTransaction.
  • Issue Transaction: An asset distribution transaction for a special transaction that distributes new assets when they are created. Use location: neo/Core/BlockChain.cs/Genesis Block.
  • Claim Transaction: NEOGAS extracts transactions for extracting GAS. There is an "Advanced" tab in the main interface of the GUI, and there is an extract GAS in the drop-down list after clicking, which calls the transaction. Use location: neo-gui/UI/ClaimForm.cs/button1_Click.
  • Enrollment Transaction: Used to apply to become a bookkeeper, that is, a member of Parliament's transaction. This transaction GUI has no access interface, that is to say, it is impossible to apply to be a bookkeeper by using GUI. Quietly: To be a bookkeeper, you need to have at least several hundred million NEOs, so if you are a bookkeeper, you should not skimp on transferring a few GAS to my NEO account.
  • Register Transaction: Register transactions for new assets, as mentioned in my previous blog, UTXO Assets from NEO Source Analysis, through which NEO and GAS were created in the Genesis Block. But interestingly, this transaction is only used when registering NEO and GAS. The way to register new assets in GUI is to make contract-level system calls to create new assets by calling "Neo.Asset.Create".
  • ContractTransaction: The most confusing thing I feel is this contract deal. I always thought that this should be used when issuing application contracts. After all, it is called contract transaction. However, goose, too young too simple, this transaction is called when we transfer money. Yes, every transfer is a contract transaction. The transaction type can be seen in the GUI's transfer history record, which is basically the contract transaction. Use location: neo/Wallets/Wallet.cs/MakeTransaction.
  • StateTransaction: As stated in the white paper, NEO network members are elected, that is to say, our common people can decide the superstructure and participate in the socialist modernization construction, which is the power NEO gives us. In the GUI, you can right-click the account and then choose to vote to vote in the election. Use location: neo-gui/UI/VotingDialog.cs/GetTransaction.
  • Publish Transaction: This is the real deal App Contract Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction Publish Transaction But what? The reality is cruel. The GUI does not call this transaction. Publishing a new contract is still a contract-level system call, creating a new contract by calling the "Neo.Contract.Create" API of vm. Code location for deploying application contracts: neo-gui/UI/DeployContractDialog.cs/GetTransaction.
  • Invocation Transaction: Replace the role of our poor Register Transaction and Publish Transaction with the pernicious type of transaction, the contract calls the transaction. The transaction we made after the successful deployment of the contract, in the type column of the GUI's transaction history, was marked with Invocation Transaction. The way to construct a transaction is even simpler and ruder, which is to call EmitSysCall directly and then pass in the parameters. Use location: neo-gui/UI/Asset RegisterDialog.cs/GetTransaction.

These are the nine types of transactions in NEO, and basically all the means of operation that can affect block chain generation except for Speaker creating new blocks. Although I have roughly analyzed each type of transaction, there are still some questions that are not clear, such as why the system calls are made when new assets are created and deployment contracts are sent instead of corresponding transactions. I can't find the answer from the source code.

0x02 balance

As can be seen from my title, I am not going to elaborate on each type of transaction. After all, the essence of the transaction is just a contract with different scripts. What I want to analyze in detail here is UTXO transaction, which is contract transaction, and also our daily transfer operation on NEO network. In the community, people are often asked, "What is the NEO account? "How do NEO and GAS balances come from?" "What exactly does the transaction turn?" I feel like I've had all these problems before. First, the concept of token is not very clear, and secondly, the concept of virtual account is even more incomprehensible. In fact, at the beginning of looking at the source code, it is also hoped that in this process to solve their own lack of awareness of block chains and smart contracts. Fortunately, with each module looking at, the overall NEO system architecture and operating principles have been basically clear. So it makes sense for the father of Linux to say, "Talk is cheap, show me your code." (Don't push, look at the code). Sorry, I'm pushing a little too much.

To understand the principle of balance calculation, first of all, we need to understand UTXO trading model. Li, the East and West community leader, has released a series of explanatory videos in the group. If you are interested, you can go to the film. Chapter 6 Transaction of Mastering BitCoin also explains it in detail. Students who want to see it can find download links at the end of the article. The code for calculating the balance is in the wallet class.

Source location: neo/Wallets/Wallet

public Fixed8 GetBalance(UInt256 asset_id)
{
    return GetCoins(GetAccounts().Select(p => p.ScriptHash))
              .Where(p => !p.State.HasFlag(CoinState.Spent)  //Unspent state
                                  && p.Output.AssetId.Equals(asset_id)) //Asset type
              .Sum(p => p.Output.Value);
}

In fact, it is clear from this that the balance calculation process is to traverse the Output corresponding to the address, and add the value of the specified asset type that is not spent (Spent stands for rolled out). The more straightforward and popular explanation is that adding every Output pointing to your account is equivalent to giving you a sum of money, and all the unspent Output adds up to your current balance.

0x03 New Deal

In NEO, when transferring money, we need to construct a Transaction object, in which we need to specify the type of assets, the amount of transfers, the source of assets (Output), the target address and the Witness. Because the transfer in GUI can construct a transaction from multiple addresses of the current wallet at the same time, which is more complex, I use the code of my small program to explain here, the basic principle is the same.

The code about transaction processing of the light wallet is changed from the TS light wallet of NEL, but the code about interface in the original code of TS is removed, and then re-encapsulated into the js module, which is equivalent to the hell-class slimming after the GUI transfer function devil slimming version, and the amount of code is greatly reduced. The entry of small program transfer is in the send.wpy interface:

Source location: neowallet for wechat/src/pages/send.wpy/OnSend

//Transaction amount
var count = NEL.neo.Fixed8.parse(that.amount + '');
//Asset class
let coin = that.checked === 'GAS' ? CoinTool.id_GAS : CoinTool.id_NEO;
wepy.showLoading({ title: 'Transaction Generation' });
//Constructing trading objects
var tran = CoinTool.makeTran(UTXO.assets, that.targetAddr, coin, count);
// console.log(tran);
//Generating the transaction id is a random number.
let randomStr = await Random.getSecureRandom(256);
//Add asset source, asset output, witness, signature
const txid = await TransactionTool.setTran(
  tran,
  prikey,
  pubkey,
  randomStr
);

Calling the makeTran method of CoinTool when constructing a transaction object requires passing in four parameters, one is OutPuts, one is the target account, the third is the asset type, and the last is the number of assets. This method corresponds to neo/Wallets/Wallet.cs/MakeTransaction<T> in neo core. Their functions are basically the same. The initialization code for the transaction object in makeTran is as follows:

        //New trading targets
        var tran = new NEL.thinneo.TransAction.Transaction();
        //The transaction type is contract transaction
        tran.type = NEL.thinneo.TransAction.TransactionType.ContractTransaction;
        tran.version = 0;//0 or 1
        tran.extdata = null;
        tran.attributes = [];
        tran.inputs = [];

In the UTXO transaction model, each transaction will have one or more sources of funding, namely those that point to the current address of the Output, these OutPuts as input for the new transaction:

        //Transaction input
        for (var i = 0; i < us.length; i++) {
            //Constructing a new input
            var input = new NEL.thinneo.TransAction.TransactionInput();
            //The prehash of the new transaction points to output
            input.hash = NEL.helper.UintHelper.hexToBytes(us[i].txid).reverse();
            input.index = us[i].n;
            input["_addr"] = us[i].addr;
            //Add a new input to the source of trading assets
            tran.inputs.push(input);
            //Calculate total added assets
            count = count.add(us[i].count);
            scraddr = us[i].addr;
            //If the number of assets added is greater than or equal to the required number, no new ones are added.
            if (count.compareTo(sendcount) > 0) {
                break;
            }
        }

In a transaction, the output of your account becomes the input of a new transaction, and then the new trade fair specifies a new output. Usually, in addition to an output pointing to the destination account, there is also an Output for finding change in a transaction. For convenience, I'd like to tell you a little story.

  • Act 1: Xiao Ming blogs to the community. Every time he writes a blog, the community will give Xiao Ming 5 GAS as a reward. Every time the community gives Xiao Ming a reward, Xiao Ming's account will have an additional 5 GAS Output. Now Xiao Ming has written three blogs, and the community has rewarded Xiao Ming three times. So Xiaoming has three outputs of 5GAS.
  • Scene 2: Xiao Ming hopes to buy cosmetics for his girlfriend with GAS earned from blogging. It is known that the price of cosmetics supported by GAS is 6.8 GAS.
  • Act 3: An Output has only 5 GAS, which is obviously not enough to pay for cosmetics, so Xiao Ming has to take out two Outputs to pay for them.
  • Act 4: Because every Output is inseparable, just like 100 yuan, you can't split a 100 piece proportionally to pay for it. You can only give 100 to others and they will give you change. This is also true of Output. If you take out two Outputs to pay for it, it is impossible to deduct 1.8 from the existing Output. We can only set both Outputs to Spent at the same time, then give the merchant a 6.8 OutPut and Xiaoming a 3.2 Output as a change. Now Xiaoming only has a 5GAS output and a 3.2GAS output.
  • Act 5: Xiao Ming purchased cosmetics for her girlfriend by her own efforts. Her girlfriend was very happy, so she bought a new washboard for Xiao Ming.

UTXO transactions are such that output can not be separated, as long as it is rolled out, it will be rolled out together, and then into a new output as a change. The output constructs the following code:

//output
if (sendcount.compareTo(NEL.neo.Fixed8.Zero) > 0) {
    var output = new NEL.thinneo.TransAction.TransactionOutput();
    //Asset type
    output.assetId = NEL.helper.UintHelper.hexToBytes(assetid).reverse();
    //Transaction amount
    output.value = sendcount;
    //Destination account
    output.toAddress = NEL.helper.Helper.GetPublicKeyScriptHash_FromAddress(targetaddr);
    //Adding Transfer Transactions
    tran.outputs.push(output);
}

//Give change
var change = count.subtract(sendcount); //Calculate the quota for finding zero
if (change.compareTo(NEL.neo.Fixed8.Zero) > 0) {
    var outputchange = new NEL.thinneo.TransAction.TransactionOutput();
    //Set the zero address to yourself
    outputchange.toAddress = NEL.helper.Helper.GetPublicKeyScriptHash_FromAddress(scraddr);
    //Setting the Zero-Finding Quota
    outputchange.value = change;
    //Types of Zero-Finding Assets
    outputchange.assetId = NEL.helper.UintHelper.hexToBytes(assetid).reverse();
    //Add Zero-Finding Trading
    tran.outputs.push(outputchange);
}

That's the process of building a new deal. Basically, the structure of a transaction has been constructed, where it comes from, where it goes, and how much it turns. Next, the transaction needs to be signed.

0x04 signature

Unlike traditional face-to-face transactions, how can transactions published in the network confirm user identity? As long as you know the other party's address, you can get the other party's Output. If only one transfer object can successfully transfer funds from the other party's account, then this is not all messy. Therefore, for a transaction, besides the elements necessary to construct the transaction, it also needs to sign the transaction and prove to the block chain that it is the owner of the account who transfers in and out of the transaction. The signature method in NEO Core is defined in the neo/Cryptography/ECC/ECDsa.cs file. Since this part belongs to cryptography and does not belong to the part I want to analyze, here is a general mention:

Source location: thinsdk-ts/thinneo/Helper.cs/Sign

 //Computing public key
 var PublicKey = ECPoint.multiply(ECCurve.secp256r1.G, privateKey);
 var pubkey = PublicKey.encodePoint(false).subarray(1, 64);
 //Get CryptoKey
 var key = new CryptoKey.ECDsaCryptoKey(PublicKey, privateKey);
 var ecdsa = new ECDsa(key);
 {
     //autograph
     return new Uint8Array(ecdsa.sign(message,randomStr));
 }

The real part of the signature is the standard ECDsa digital signature. The return value is a 64-byte Uint8 array. The first 32 bytes are R and the last 32 bytes are S:

let arr = new Uint8Array(64);
Arrayhelper.copy(r.toUint8Array(false, 32), 0, arr, 0, 32);
Arrayhelper.copy(s.toUint8Array(false, 32), 0, arr, 32, 32);
return arr.buffer;

Parameters S and R are very important parameters in ECDsa digital signature verification.

0x05 Witness

It is not enough to calculate the signature. We also need to add the signature to the transaction. This process is to add witnesses:

tran.AddWitness(signdata, pubkey, WalletHelper.wallet.address);

In fact, the process of adding witnesses is to push the signature information of the previous step and the verification information obtained through the public key into the witness script, and delete the witness adding process of the complex verification process as follows:

Source location: thinsdk-ts/thinneo/Transaction.cs

//Adding witnesses to personal accounts (signing a transaction with the person's private key, signdata coming in)
 public AddWitness(signdata: Uint8Array, pubkey: Uint8Array, addrs: string): void {
    var vscript = Helper.GetAddressCheckScriptFromPublicKey(pubkey);
    //iscript is a pushbytes directive to a personal account witness
    var sb = new ScriptBuilder();
    sb.EmitPushBytes(signdata);
    var iscript = sb.ToArray();
    this.AddWitnessScript(vscript, iscript);
}

//Increasing Witnesses for Intelligent Contracts
public AddWitnessScript(vscript: Uint8Array, iscript: Uint8Array): void {
    var newwit = new Witness();
    newwit.VerificationScript = vscript;
    newwit.InvocationScript = iscript;
    //Adding new witnesses
    this.witnesses.push(newwit);

}

Here I still have a problem, that is, this transaction involves multiple accounts under a wallet, should there be multiple signatures? In theory, yes, after all, each account has its own private key, so I went to see the GUI transfer code. The entry for the GUI to get a transaction and sign it is the transfer method in the MainForm file, which calls SignAndShowInformation in Helper. In this SignAndShowInformation, the Sign method in the Wallet file is called, and the context of the transaction is passed in:

Source location: neo/Wallets/Wallet.cs

public bool Sign(ContractParametersContext context)
{
    bool fSuccess = false;
    foreach(UInt160 scriptHash in context.ScriptHashes)
    {
        WalletAccount account = GetAccount(scriptHash);
        if (account ?.HasKey != true) continue;
        KeyPair key = account.GetKey();
        byte[] signature = context.Verifiable.Sign(key);
        fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature);
    }
    return fSuccess;
}

From the source code, it can be seen that NEO uses the private key of each account involved in the transaction to sign the transaction. After the signature is completed, the GetScripts method is called to get the script. In this method, several witnesses are added to the transaction:

public Witness[] GetScripts()
{
    if (!Completed) throw new InvalidOperationException();
    // Number of script hashes = number of witnesses
    Witness[] scripts = new Witness[ScriptHashes.Count];
    for (int i = 0; i < ScriptHashes.Count; i++)
    {
        ContextItem item = ContextItems[ScriptHashes[i]];
        using(ScriptBuilder sb = new ScriptBuilder())
        {
            foreach(ContractParameter parameter in item.Parameters.Reverse())
            {
                sb.EmitPush(parameter);
            }
            //Adding new witnesses
            scripts[i] = new Witness
            {
                InvocationScript = sb.ToArray(),
                    VerificationScript = item.Script ?? new byte[0]
             };
        }
    }
    return scripts;
}

So if your GUI wallet transfers more money outward than a single account, it's actually a multi-account transaction.

Download link: Mastering Bit Coin: https://github.com/Liaojinghui/BlockChainBooks

Posted by vtbruinsfan on Tue, 14 May 2019 15:51:17 -0700