Eventually content please refer to the original: https://wangwei.one/posts/f9088e0f.html

The introduction

In the last article, we started implementing a trading mechanism. You’ve seen some of the non-personal characteristics of transactions: There is no user account, your personal data (e.g., name, passport number, and SSN) is not required, and is not stored anywhere in Bitcoin. But there still has to be something that identifies you as the owner of the outputs of those transactions (for example, the owner of the coins locked to those outputs). That’s where bitcoin addresses come in. So far, we’ve just used arbitrary user-defined strings as addresses. Now it’s time to implement real addresses, just like they are implemented in Bitcoin.

Bitcoin address

There is a currency address sample: 1 a1zp1ep5qgefi2dmptftl5slmv7divfna. This is a very early Bitcoin address, purportedly belonging to Satoshi Nakamoto. Bitcoin addresses are public. If you want to send bitcoin to someone, you need to know their Bitcoin address. But an address (unique though it is) is no proof that you are a wallet owner. In fact, such an address is a more readable representation of the public key. In Bitcoin, your identity is a pair (or pairs) of private and public keys stored on your computer (or in some other location you have access to). Bitcoin relies on a combination of encryption algorithms to create these keys and guarantees that no one else in the world can access your bitcoin without a physical access key.

Bitcoin addresses are different from public keys. The bitcoin address is generated by a public key through a one-way hash function

Next, let’s discuss these encryption algorithms.

Note: Do not test by sending real Bitcoins to any bitcoin address generated by the code in this article at your own risk…

Public key cryptography

Public-key cryptography uses key pairs: public and private keys. Public keys are non-sensitive information and can be disclosed to anyone. In contrast, a private key cannot be publicly disclosed: no one but the owner can have access to the private key because it is used as the owner’s identity. Your private key represents you (in the cryptocurrency world, of course).

In essence, a Bitcoin wallet is a pair of such keys. When you install a wallet app or use a Bitcoin client to generate a new address, they create a key pair for you. In bitcoin, whoever controls the private key controls all bitcoins sent to the corresponding public key address.

Private and public keys are just random sequences of bytes, so they cannot be printed on the screen for human reading. That’s why Bitcoin uses an algorithm to turn the byte sequence of a public key into a human-readable string.

If you’ve ever used the Bitcoin Wallet app, it might generate a mnemonic passphrase for you. These mnemonics can be used in place of private keys and can generate private keys. This mechanism is implemented through BIP-039.

Ok, so now we know what determines a user’s identity in Bitcoin. But how does bitcoin verify ownership of the transaction output (and some of the coins it stores)?

A digital signature

In mathematics and cryptography, there is the concept of a digital signature, and this algorithm guarantees the following:

  1. Ensure that data will not be tampered with during transmission from sender to receiver;
  2. The data is created by a sender;
  3. The sender cannot repudiate the data sent;

By applying a signature algorithm to the data (that is, signing the data), you get a signature that can be verified later. Digital signatures require a private key, whereas authentication requires a public key.

In order to be able to sign data we need:

  1. Data used to be signed;
  2. The private key.

The signing operation produces a signature stored in the transaction input. To be able to verify a signature, we need:

  1. Data after signature;
  2. The signature;
  3. The public key.

In simple terms, the verification process can be described as checking that the signature is derived from the signed data plus the private key, and that the public key is generated by the private key.

A digital signature is not a method of encryption, and you cannot reverse the construction of the source data from the signature. This is similar to the Hash algorithm we mentioned earlier: by applying a Hash algorithm to a piece of data, you get a unique representation of that data. The difference between the two is that the signature algorithm has an extra key pair: it makes it possible to verify digital signatures.

But key pairs can also be used to encrypt data: the private key is used to encrypt data, and the public key is used to decrypt data. But bitcoin doesn’t use encryption algorithms.

In Bitcoin, each transaction entry is signed by the creator of that transaction. Every transaction in Bitcoin must be verified before it can be placed in a block. Verification means:

  • Check whether the trade input has permission to reference the trade output from the previous trade
  • Check that the transaction is signed correctly

The process of data signature and signature verification is as follows:

Let’s review the full life cycle of a deal:

  1. Initially, there will be a Genesis block containing Coinbase transactions. Since there is no real transaction entry in a Coinbase transaction, it does not require a signature. The output of a Coinbase transaction will contain a post-hashing public key (using an algorithm ofRIPEMD16(SHA256(PubKey))
  2. When a person sends a bitcoin, a transaction is created. The transaction inputs for this transaction reference the transaction outputs for the previous transaction or transactions. Each transaction input will store the unhashing public key as well as the signing information for the entire transaction.
  3. When other nodes in the Bitcoin network receive transaction data broadcast by other nodes, it will be verified. Among other things, they will verify:
    • Checking whether the Hash value of the public key in the transaction input matches the Hash value of the output of the transaction it references ensures that the sender can only send their own bitcoins.
    • Check that the signature is correct, which is to ensure that the transaction was created by the true owner of Bitcoin.
  4. When a miner is ready to start mining a new block, he puts the transaction information into the block and starts digging.
  5. When a block is mined, other nodes in the network receive a message that the block is mined, and they add the block to the blockchain.
  6. When a block is added to the blockchain, it signals that the transaction has been completed, and the resulting transaction output is referenced in new transactions.

Elliptic curve cryptography

As mentioned earlier, public and private keys are random sequences of characters. Since the private key is used to identify the owner of the bitcoin, there is a necessary condition: the random algorithm must produce a truly random sequence. We don’t want to accidentally generate a private key that someone else owns. In other words, we want to ensure that random sequences are absolutely unique.

Bitcoin is a private key generated using an elliptic curve. Elliptic curves are a very complex mathematical concept that we won’t cover in detail here (if you’re curious, click this Gentle Introduction to Elliptic Curves for details, warning: Math formula). What we need to know is that these curves can be used to generate really large and random numbers. Bitcoin uses a curve algorithm that randomly generates a number between 0 and 2^2^56 (which is a very large number, about 10^77 in decimal terms, compared with the number of atoms in the visible universe ranging from 10^78 to 10^82). This large upper limit means that generating two identical private keys is almost impossible.

In addition, we will use ECDSA (Elliptic Curve Digital Signature Algorithm) used in Bitcoin to sign the transaction information.

Base58 and Base58Check encoding

Now let’s go back to the above mentioned COINS address: 1 a1zp1ep5qgefi2dmptftl5slmv7divfna. We now know that this address is actually a readable representation of the public key. If we decode it, we see that the public key looks like this (the hexadecimal representation of a sequence of bytes) :

0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93
Copy the code

Base58

Base64 uses 26 lower-case letters, 26 upper-case letters, 10 numbers, and two symbols (such as “+” and “/”) for transmitting binary data in a text-based medium like E-mail. Base64 is commonly used to encode attachments in mail. Base58 is a text-based binary encoding format used in Bitcoin and other cryptocurrencies. This encoding format not only achieves data compression, maintains legibility, but also has error diagnosis function. Base58 is a subset of the Base64 encoding format, which also uses upper and lower case letters and 10 digits, but leaves out some characters that are easily misread and confused in certain fonts. Specifically, Base58 does not contain the characters “0” (digit 0), “O” (uppercase O), “L” (lowercase L), “I” (uppercase I), and “+” and “/” in Base64. Simply put, Base58 consists of upper and lower case letters and numbers that do not include (0, O, L, I).

The Base58 alphabet of Bitcoin:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

Base58Check

Base58Check is a Base58 encoding commonly used in bitcoin that adds error-checking codes to check for errors in transcriptions of data. The verification code is 4 bytes long and is added after the data to be encoded. Verification codes are derived from hashes of the data to be encoded, so they can be used to detect and avoid errors in transcription and input. If the Base58check encoding format is used, the encoding software calculates the verification code of the original data and compares it with the verification code in the result data. A mismatch indicates an error, and the Base58Check data is invalid. For example, an incorrect Bitcoin address will not be recognized as a valid address by a wallet, or the error will result in the loss of funds.

To encode data (numbers) using the Base58Check encoding format, we first add a prefix called “version byte” to the data, which identifies the type of data to encode. For example, bitcoin addresses are prefixed with 0 (0x00 in hexadecimal), whereas private keys are encoded with 128 (0x80 in hexadecimal).

Let’s show the process of getting an address from a public key in schematic form:

Therefore, the decoded public key is composed of three parts:

Version  Public key hash                           Checksum
00       62E907B15CBF27D5425399EBF6F0FB50EBB88F18  C29B7D93
Copy the code

Since the hash function is one-way (i.e. cannot be reversed), it is not possible to extract the public key from a hash. However, by performing the hash function and performing the hash comparison, we can check whether a public key is used in the generation of the hash.

OK, now that we have everything, let’s write some code. When concepts are written in code, we understand them more clearly and deeply.

Address implementation

Let’s start with the Wallet structure, where we need to introduce a Maven package:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.59</version>
</dependency>
Copy the code

The wallet structure

/** ** wallet **@author wangwei
 * @date2018/03/14 * /
@Data
@AllArgsConstructor
public class Wallet {

    // Check code length
    private static final int ADDRESS_CHECKSUM_LEN = 4;
    /** * private key */
    private BCECPrivateKey privateKey;
    /** * Public key */
    private byte[] publicKey;

    public Wallet(a) {
        initWallet();
    }

    /** * initializes the wallet */
    private void initWallet(a) {
        try {
            KeyPair keyPair = newECKeyPair();
            BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
            BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();

            byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);

            this.setPrivateKey(privateKey);
            this.setPublicKey(publicKeyBytes);
        } catch(Exception e) { e.printStackTrace(); }}/** * Creates a new key pair **@return
     * @throws Exception
     */
    private KeyPair newKeyPair(a) throws Exception {
        // Register BC Provider
        Security.addProvider(new BouncyCastleProvider());
        // Create a key pair generator for the elliptic curve algorithm, ECDSA
        KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
        // Elliptic curve (EC) field parameter Settings
        / / why choose secp256k1 bitcoins, see: https://bitcointalk.org/index.php?topic=151120.0
        ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
        g.initialize(ecSpec, new SecureRandom());
        returng.generateKeyPair(); }}Copy the code

A wallet is essentially a key pair. Here we need to use the KeyPairGenerator to generate the key pair.

Next, let’s generate the wallet address of bitcoin:

public class Wallet {.../** * get the wallet address **@return* /
    public String getAddress(a) throws Exception {
        Obtain ripemdHashedKey
        byte[] ripemdHashedKey = BtcAddressUtils.ripeMD160Hash(this.getPublicKey().getEncoded());

        // 2. Add version 0x00
        ByteArrayOutputStream addrStream = new ByteArrayOutputStream();
        addrStream.write((byte) 0);
        addrStream.write(ripemdHashedKey);
        byte[] versionedPayload = addrStream.toByteArray();

        // 3. Calculate the parity code
        byte[] checksum = BtcAddressUtils.checksum(versionedPayload);

        Paylod + checksum (version + paylod + checksum)
        addrStream.write(checksum);
        byte[] binaryAddress = addrStream.toByteArray();

        // 5. Perform Base58 conversion
        returnBase58Check.rawBytesToBase58(binaryAddress); }... }Copy the code

At this point, you’ll get the actual Bitcoin address, and you can check the balance at blockchain.info.

Through the getAddress method, for example, get a currency address is: 1 rz9sjxmrwnbw3pu8itc1htnbvhersqhaacbl16

I can guarantee that no matter how many times you generate a Bitcoin address, it will always have a balance of 0. That’s why choosing the appropriate public-key cryptography algorithm is so important: Given that the private key is a random number, the chances of producing the same number must be as low as possible. Ideally, it must be as low as “never.”

Also, it’s important to note that you don’t need to connect to a bitcoin node to get the bitcoin address. Open source algorithmic toolkits for address generation have been implemented in many programming languages and libraries.

Now we need to modify the transaction inputs and outputs so that they start using real addresses:

Transaction input

/** * Transaction input **@author wangwei
 * @date2017/03/04 * /
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TXInput {

    /** * Hash value of the transaction Id */
    private byte[] txId;
    /** * Transaction output index */
    private int txOutputIndex;
    /** * signature */
    private byte[] signature;
    /** * Public key */
    private byte[] pubKey;


    /** * Checks whether the public key hash is used for the transaction input **@param pubKeyHash
     * @return* /
    public boolean usesKey(byte[] pubKeyHash) {
        byte[] lockingHash = BtcAddressUtils.ripeMD160Hash(this.getPubKey());
        returnArrays.equals(lockingHash, pubKeyHash); }}Copy the code

Trading output

/** * transaction output **@author wangwei
 * @date2017/03/04 * /
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TXOutput {

    /** * Value */
    private int value;
    /** * Public key Hash */
    private byte[] pubKeyHash;

    /** * Create transaction output **@param value
     * @param address
     * @return* /
    public static TXOutput newTXOutput(int value, String address) {
        // Reverse conversion to byte array
        byte[] versionedPayload = Base58Check.base58ToBytes(address);
        byte[] pubKeyHash = Arrays.copyOfRange(versionedPayload, 1, versionedPayload.length);
        return new TXOutput(value, pubKeyHash);
    }

    /** * Checks whether the transaction output can use the specified public key **@param pubKeyHash
     * @return* /
    public boolean isLockedWithKey(byte[] pubKeyHash) {
        return Arrays.equals(this.getPubKeyHash(), pubKeyHash); }}Copy the code

There are many other changes in the code that need to be made, but I won’t point them out here, see the source link at the end of this article.

Note that we no longer use the scriptPubKey and scriptSig fields because we will not implement scripting language features. Instead, we split scriptSig into signature and pubKey fields, and scriptPubKey was renamed to pubKeyHash. We will implement signature logic similar to transaction output lock/unlock logic and transaction input logic in Bitcoin, but we will do this in methods.

UsesKey is used to check whether the public key in the trade input can unlock the trade output. Note that the unhashed public key is stored in the transaction input, but the method implementation does a ripeMD160Hash conversion to it.

IsLockedWithKey is used to check whether the supplied public key Hash can be used to unlock the transaction output. This method complements usesKey. UsesKey is used in the getAllSpentTXOs method and isLockedWithKey is used in the findUnspentTransactions method, which makes a connection between the two transactions.

In the newTXOutput method, value is locked to address. When we send bitcoin to someone, we only know their address, so the function takes the address as its only argument. The address is then decoded and the public key hash extracted from it is saved in the PubKeyHash field.

Now, let’s check to see if it works:

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1BCY5gCXUMiFYc5ieBMfEUaZn3GYkvVZ2e

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 19aomsC58CQ1tPzNLx7kV9yjk1pqZtSzL1

$ java -jar blockchain-java-jar-with-dependencies.jar  createblockchain -address 13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh

Elapsed Time: 6.77 seconds 
correct hash Hex: 00000e44be0c94c39a4fef24c67d85c428e8bfbd227e292d75c0f4d398e2e81c 

Done ! 

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh
Balance of '13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh': 10

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1BCY5gCXUMiFYc5ieBMfEUaZn3GYkvVZ2e -to  13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVd -amount 5
java.lang.Exception: ERROR: Not enough funds

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh -to 1BCY5gCXUMiFYc5ieBMfEUaZn3GYkvVZ2e-amount 5
Elapsed Time: 4.477 seconds 
correct hash Hex: 00000da41dfacc8032a553ed5b1aa5e24318d5d89ca14a16c4f70129609c8365 

Success!

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh
Balance of '13dJAkeMyjjXvWCmhsXpDqnszHvhFSLVdh': 5

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1BCY5gCXUMiFYc5ieBMfEUaZn3GYkvVZ2e
Balance of '1BCY5gCXUMiFYc5ieBMfEUaZn3GYkvVZ2e': 5

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 19aomsC58CQ1tPzNLx7kV9yjk1pqZtSzL1
Balance of '19aomsC58CQ1tPzNLx7kV9yjk1pqZtSzL1': 0
Copy the code

Nice! Now let’s implement the signature part of the transaction.

Signature implementation

Transaction data must be signed, as this is the only way in Bitcoin to guarantee that you can’t spend bitcoins that belong to someone else. If a signature is invalid, then the transaction is also invalid, in which case the transaction cannot be added to the blockchain.

We have all the pieces to implement the transaction signature, except for one thing: the data for the signature. What part of the transaction data is actually used for signing? Is it all the data? Choosing the data to use for signing is quite important. The data used for signing must contain information that identifies the data in a unique and unique way. For example, it does not make sense to sign only the transaction output because this signature does not consider the sender and receiver.

Considering that the transaction data unlocks the previous transaction output, reallocates the value in the transaction output, and locks the new transaction output, the following data must be signed:

  1. Public key Hash stored in the unlocked transaction output. It identifies the sender of the transaction.
  2. Public key Hash stored in the new, locked transaction output. It identifies the recipient of the transaction.
  3. Contained in the new transaction outputvalueValue.

In Bitcoin, the lock/unlock logic is stored in the script, the unlock script is stored in the ScriptSig field of the transaction input, and the lock script is stored in the ScriptPubKey field of the transaction output. Since bitcoin allows different types of scripts, it signs the entire contents of a ScriptPubKey.

As you can see, we do not need to sign the public key stored in the transaction input. Because of this, in Bitcoin, what is signed is not a transaction, but a partitioned copy of the transaction input that stores the ScriptPubKey of the referenced transaction output.

The detailed procedure for obtaining a trimmed copy of the transaction is here. It may be out of date, but I haven’t found a more reliable source.

OK, it seems a little complicated, so let’s start coding. We’ll start with the Sign method:

public class Transaction {.../** ** signature **@paramPrivateKey private key *@paramPrevTxMap set of previous multiple transactions */
    public void sign(BCECPrivateKey privateKey, Map<String, Transaction> prevTxMap) throws Exception {
        // Coinbase transaction information does not require a signature because it does not have transaction input information
        if (this.isCoinbase()) {
            return;
        }
        // Verify that the transaction input in the transaction information is correct, i.e. can find the corresponding transaction data
        for (TXInput txInput : this.getInputs()) {
            if (prevTxMap.get(Hex.encodeHexString(txInput.getTxId())) == null) {
                throw new Exception("ERROR: Previous transaction is not correct"); }}// Create a copy of the transaction information for signing
        Transaction txCopy = this.trimmedCopy();
      
        Security.addProvider(new BouncyCastleProvider());
        Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
        ecdsaSign.initSign(privateKey);

        for (int i = 0; i < txCopy.getInputs().length; i++) {
            TXInput txInputCopy = txCopy.getInputs()[i];
            // Get transaction data corresponding to TxID
            Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInputCopy.getTxId()));
            // Get the trade output from the previous trade corresponding to the trade input
            TXOutput prevTxOutput = prevTx.getOutputs()[txInputCopy.getTxOutputIndex()];
            txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());
            txInputCopy.setSignature(null);
            // Get the data to be signed, i.e. the transaction ID
            txCopy.setTxId(txCopy.hash());
            txInputCopy.setPubKey(null);

            // Only the transaction ID is signed
            ecdsaSign.update(txCopy.getTxId());
            byte[] signature = ecdsaSign.sign();

            // Assign the signature of the entire transaction data to the transaction input, since the transaction input requires a signature containing the entire transaction information
            // Note that the resulting signature is assigned to the transaction input in the original transaction information
            this.getInputs()[i].setSignature(signature); }}... }Copy the code

This method takes the private key and a collection of previous transactions as parameters. As mentioned earlier, in order to be able to sign the transaction information, we need to be able to access the transaction outputs referenced by the transaction inputs in the transaction data, so we need to get the transaction information that stores these transaction outputs.

Let’s review this method step by step:

if (this.isCoinbase()) {
   return;
}
Copy the code

Since coinbase transaction information does not have transaction input information, it does not require a signature and simply returns.

Transaction txCopy = this.trimmedCopy();
Copy the code

Create a copy of the transaction

public class Transaction {.../** * Create a copy of the transaction data for signing **@return* /
    public Transaction trimmedCopy(a) {
        TXInput[] tmpTXInputs = new TXInput[this.getInputs().length];
        for (int i = 0; i < this.getInputs().length; i++) {
            TXInput txInput = this.getInputs()[i];
            tmpTXInputs[i] = new TXInput(txInput.getTxId(), txInput.getTxOutputIndex(), null.null);
        }

        TXOutput[] tmpTXOutputs = new TXOutput[this.getOutputs().length];
        for (int i = 0; i < this.getOutputs().length; i++) {
            TXOutput txOutput = this.getOutputs()[i];
            tmpTXOutputs[i] = new TXOutput(txOutput.getValue(), txOutput.getPubKeyHash());
        }

        return new Transaction(this.getTxId(), tmpTXInputs, tmpTXOutputs); }... }Copy the code

This copy of the transaction data contains the transaction input and the transaction output, but the Signature and PubKey of the transaction input need to be set to NULL.

Initializing the SHA256withECDSA signature algorithm with the private key:

Security.addProvider(new BouncyCastleProvider());
        Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
        ecdsaSign.initSign(privateKey);
Copy the code

Next, we iterate over the trade input in the trade copy:

for (TXInput txInput : txCopy.getInputs()) {
      // Get transaction data corresponding to TxID
      Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInputCopy.getTxId()));
      // Get the trade output from the previous trade corresponding to the trade input
      TXOutput prevTxOutput = prevTx.getOutputs()[txInputCopy.getTxOutputIndex()];
      txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());
      txInputCopy.setSignature(null);
Copy the code

In each txInput, signature needs to be set to NULL (only for a secondary confirmation check), and pubKey is set to the pubKeyHash field output by the transaction it references. At this point, all transaction inputs except the current circulating transaction input (txInput) are “empty”, meaning their Signature and PubKey fields are set to null. Therefore, transaction inputs are signed separately, although this is not important for our application, bitcoin allows transactions to contain inputs that reference different addresses.

The Hash method serializes the transaction and hashes it using the SHA-256 algorithm. The result of the hash is the data we want to sign. After getting the hash, we should reset the PubKey field so that it does not affect subsequent iterations.

// Get the data to be signed, i.e. the transaction ID
txCopy.setTxId(txCopy.hash());
txInput.setPubKey(null);
Copy the code

Now comes the crucial part:

// Only the transaction ID is signed
Security.addProvider(new BouncyCastleProvider());
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA",BouncyCastleProvider.PROVIDER_NAME);
ecdsaSign.initSign(privateKey);
ecdsaSign.update(txCopy.getTxId());
byte[] signature = ecdsaSign.sign();

// Assign the signature of the entire transaction data to the transaction input, since the transaction input requires a signature containing the entire transaction information
// Note that the resulting signature is assigned to the transaction input in the original transaction information
this.getInputs()[i].setSignature(signature);
Copy the code

SHA256withECDSA signature algorithm and private key are used to sign the transaction ID, so as to obtain the transaction signature to be set for the transaction input.

Now, let’s implement the transaction verification function:

public class Transaction {.../** * verify transaction information **@paramPrevTxMap front set of multiple transactions *@return* /
    public boolean verify(Map<String, Transaction> prevTxMap) throws Exception {
        // Coinbase transaction information does not need to be signed, and therefore does not need to be verified
        if (this.isCoinbase()) {
            return true;
        }

        // Verify that the transaction input in the transaction information is correct, i.e. can find the corresponding transaction data
        for (TXInput txInput : this.getInputs()) {
            if (prevTxMap.get(Hex.encodeHexString(txInput.getTxId())) == null) {
                throw new Exception("ERROR: Previous transaction is not correct"); }}// Create a copy of the transaction information for signature verification
        Transaction txCopy = this.trimmedCopy();
        
        Security.addProvider(new BouncyCastleProvider());
        ECParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("secp256k1");
        KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
        Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
        
        for (int i = 0; i < this.getInputs().length; i++) {
            TXInput txInput = this.getInputs()[i];
            // Get transaction data corresponding to TxID
            Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInput.getTxId()));
            // Get the trade output from the previous trade corresponding to the trade input
            TXOutput prevTxOutput = prevTx.getOutputs()[txInput.getTxOutputIndex()];

            TXInput txInputCopy = txCopy.getInputs()[i];
            txInputCopy.setSignature(null);
            txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());
            // Get the data to be signed, i.e. the transaction ID
            txCopy.setTxId(txCopy.hash());
            txInputCopy.setPubKey(null);
            
            // Use elliptic curves x,y points to generate the public Key
            BigInteger x = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 1.33));
            BigInteger y = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 33.65));
            ECPoint ecPoint = ecParameters.getCurve().createPoint(x, y);

            ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            ecdsaVerify.initVerify(publicKey);
            ecdsaVerify.update(txCopy.getTxId());
            if(! ecdsaVerify.verify(txInput.getSignature())) {return false; }}return true; }... }Copy the code

First, we get a copy of the transaction as before:

Transaction txCopy = this.trimmedCopy();
Copy the code

Get elliptic curve parameters and signature classes:

Security.addProvider(new BouncyCastleProvider());
        ECParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("secp256k1");
        KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
        Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
Copy the code

Next, let’s check if the signature entered for each transaction is correct:

for (int i = 0; i < this.getInputs().length; i++) {
    TXInput txInput = this.getInputs()[i];
    // Get transaction data corresponding to TxID
    Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInput.getTxId()));
    // Get the trade output from the previous trade corresponding to the trade input
    TXOutput prevTxOutput = prevTx.getOutputs()[txInput.getTxOutputIndex()];

    TXInput txInputCopy = txCopy.getInputs()[i];
    txInputCopy.setSignature(null);
    txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());
    // Get the data to be signed, i.e. the transaction ID
    txCopy.setTxId(txCopy.hash());
    txInputCopy.setPubKey(null);
}    
Copy the code

This part is the same as in the Sign method because we need to Sign the same data during validation.

// Use elliptic curves x,y points to generate the public Key
BigInteger x = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 1.33));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 33.65));
ECPoint ecPoint = ecParameters.getCurve().createPoint(x, y);

ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(txCopy.getTxId());
if(! ecdsaVerify.verify(txInput.getSignature())) {return false;
}
Copy the code

As the pubkey stored in the transaction input is actually a pair of x and y coordinates on the elliptic curve, we can obtain the PublicKey PublicKey from pubkey, and then use the PublicKey to sign for verification. Returns true if validation succeeds, false otherwise.

Now, we need a way to retrieve the previous transaction. Since this requires interaction with blockchain, we will make it a method of blockchain:

public class Blockchain {.../** * Query transaction information by transaction ID **@paramTxId Transaction ID *@return* /
    private Transaction findTransaction(byte[] txId) throws Exception {
        for (BlockchainIterator iterator = this.getBlockchainIterator(); iterator.hashNext(); ) {
            Block block = iterator.next();
            for (Transaction tx : block.getTransactions()) {
                if (Arrays.equals(tx.getTxId(), txId)) {
                    returntx; }}}throw new Exception("ERROR: Can not found tx by txId ! ");
    }


    /** * sign the transaction **@paramTx transaction data *@paramPrivateKey private key * /
    public void signTransaction(Transaction tx, BCECPrivateKey privateKey) throws Exception {
        // Start by finding the data from previous transactions referenced by the transaction input in this new transaction
        Map<String, Transaction> prevTxMap = new HashMap<>();
        for (TXInput txInput : tx.getInputs()) {
            Transaction prevTx = this.findTransaction(txInput.getTxId());
            prevTxMap.put(Hex.encodeHexString(txInput.getTxId()), prevTx);
        }
        tx.sign(privateKey, prevTxMap);
    }

    /** * Transaction signature verification **@param tx
     */
    private boolean verifyTransactions(Transaction tx) throws Exception {
        Map<String, Transaction> prevTx = new HashMap<>();
        for (TXInput txInput : tx.getInputs()) {
            Transaction transaction = this.findTransaction(txInput.getTxId());
            prevTx.put(Hex.encodeHexString(txInput.getTxId()), transaction);
        }
        returntx.verify(prevTx); }}Copy the code

Now we need to actually sign and verify our transaction, which takes place in newUTXOTransaction:

 public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {... Transaction newTx =new Transaction(null, txInputs, txOutput);
    newTx.setTxId(newTx.hash());

    // Sign the transaction
    blockchain.signTransaction(newTx, senderWallet.getPrivateKey());

    return newTx;
}
Copy the code

Validation of a transaction occurs before a transaction is placed in a block:

public void mineBlock(Transaction[] transactions) throws Exception {
    // Verify transaction records before mining
    for (Transaction tx : transactions) {
        if (!this.verifyTransactions(tx)) {
           throw new Exception("ERROR: Fail to mine block ! Invalid transaction ! "); }}... }Copy the code

OK, let’s do a test on the entire project code again, and the test result is:

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1NnmFCuNnhPZHfXu38wZi8uEb446pDhaGB

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 13K6rfHPifjdH4HXN2okpo4uxNRfVCx13f

$ java -jar blockchain-java-jar-with-dependencies.jar  createblockchain -address 1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6

Elapsed Time: 164.961 seconds 
correct hash Hex: 00000524231ae1832c49957848d2d1871cc35ff4d113c23be1937c6dff5cdf2a 

Done ! 

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6
Balance of '1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6': 10

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1NnmFCuNnhPZHfXu38wZi8uEb446pDhaGB -to  13K6rfHPifjdH4HXN2okpo4uxNRfVCx13f -amount 5
java.lang.Exception: ERROR: Not enough funds

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6 -to 1NnmFCuNnhPZHfXu38wZi8uEb446pDhaGB -amount 5
Elapsed Time: 54.92 seconds 
correct hash Hex: 00000354f86cde369d4c39d2b3016ac9a74956425f1348b4c26b2cddb98c100b 

Success!


$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6
Balance of '1GTh9Yjh4eH2a69FMX2kvSpnkJAgLdXFD6': 5

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1NnmFCuNnhPZHfXu38wZi8uEb446pDhaGB
Balance of '1NnmFCuNnhPZHfXu38wZi8uEb446pDhaGB': 5

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 13K6rfHPifjdH4HXN2okpo4uxNRfVCx13f
Balance of '13K6rfHPifjdH4HXN2okpo4uxNRfVCx13f': 0
Copy the code

Good! There are no mistakes!

Let’s comment out a line of code in the NewUTXOTransaction method to ensure that unsigned transactions cannot be added to the block:

blockchain.signTransaction(newTx, senderWallet.getPrivateKey());
Copy the code

Test results:

java.lang.Exception: Fail to verify transaction ! transaction invalid ! 
	at one.wangwei.blockchain.block.Blockchain.verifyTransactions(Blockchain.java:334)
	at one.wangwei.blockchain.block.Blockchain.mineBlock(Blockchain.java:76)
	at one.wangwei.blockchain.cli.CLI.send(CLI.java:202)
	at one.wangwei.blockchain.cli.CLI.parse(CLI.java:79)
	at one.wangwei.blockchain.BlockchainTest.main(BlockchainTest.java:23)
Copy the code

conclusion

In this episode, we learned:

  1. Using elliptic curve encryption algorithm, how to create a wallet;
  2. Learned how to generate bitcoin addresses;
  3. How to sign the transaction information and verify the signature;

So far we have implemented many of the key features of Bitcoin! We have implemented almost everything except networking, and in the next article we will continue to refine the transactions part of the mechanic.

data

  • Source: https://github.com/wangweiX/blockchain-java/tree/part5-wallet

  • Elliptic Curve Key Pair Generation and Key Factories

  • How to create public key objects with x and y coordinates?

  • Public-key cryptography

  • Digital signatures

  • Elliptic curve

  • Elliptic curve cryptography

  • ECDSA

  • Technical background of Bitcoin addresses

  • Address

  • Base58

  • A gentle introduction to elliptic curve cryptography