If you have ever used a Bitcoin wallet as an application, it is likely that a mnemonic phrase was created for you. Such phrases are used instead of private keys and can be used to generate them. This mechanism is implemented in BIP-039 .
Digital signatures are not encrypted, and data cannot be obtained from it. This is similar to hashing: you convert data using an algorithm and get their unique representation. The difference between the hash and the signature is the key pair that allows you to verify the latter.
But such key pairs can also be used to encrypt data: the private key is used for encryption, and the public key is used for decryption. However, Bitcoin does not use encryption algorithms.

RIPEMD16(SHA256(PubKey)) algorithms are used RIPEMD16(SHA256(PubKey)) ). 0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93 
Version 00 Public key hash 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 Checksum C29B7D93 type Wallet struct { PrivateKey ecdsa.PrivateKey PublicKey []byte } type Wallets struct { Wallets map[string]*Wallet } func NewWallet() *Wallet { private, public := newKeyPair() wallet := Wallet{private, public} return &wallet } func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) return *private, pubKey } Wallets type to store a collection of wallets, save them to a file and unload them from it. A new key pair is created in the Wallet constructor. The newKeyPair function newKeyPair simple, here we use ECDSA. The private key is then created using a curve, and the public key is generated using the private key. One note: in elliptic curve based algorithms, public keys are points on a curve. Thus, a public key is a combination of X, Y coordinates. In Bitcoin, these coordinates are combined to form a public key. func (w Wallet) GetAddress() []byte { pubKeyHash := HashPubKey(w.PublicKey) versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) fullPayload := append(versionedPayload, checksum...) address := Base58Encode(fullPayload) return address } func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) RIPEMD160Hasher := ripemd160.New() _, err := RIPEMD160Hasher.Write(publicSHA256[:]) publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) return publicRIPEMD160 } func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) return secondSHA[:addressChecksumLen] } RIPEMD160 (SHA256 (PubKey)) hashing algorithms.SHA256 (SHA256 (payload)) . The checksum is the first four bytes of the received hash.version+PubKeyHash .version+PubKeyHash+checksum combination using Base58. type TXInput struct { Txid []byte Vout int Signature []byte PubKey []byte } func (in *TXInput) UsesKey(pubKeyHash []byte) bool { lockingHash := HashPubKey(in.PubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } type TXOutput struct { Value int PubKeyHash []byte } func (out *TXOutput) Lock(address []byte) { pubKeyHash := Base58Decode(address) pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] out.PubKeyHash = pubKeyHash } func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 } ScriptPubKey and ScriptSig , instead ScriptSig is divided into Signature and PubKey , and ScriptPubKey renamed to PubKeyHash . We will implement the same functions of blocking / unblocking outputs and logic of input signatures, as in Bitcoin, but we will implement this using methods.UsesKey method verifies that the input uses a specific key to unlock the output. Please note that the inputs store unshaded public keys, and the function accepts hashed. IsLockedWithKey checks if the hash key of the public key was used to block the output. This is an optional feature for UsesKey , and both are used in FindUnspentTransactions to build connections between transactions.Lock simply blocks the exit. When we send coins to someone, we only know the address, so the function takes the address as the only argument. Then the address is decoded, and the public key hash key is extracted from it and stored in the PubKeyHash field. $ blockchain_go createwallet Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt $ blockchain_go createwallet Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h $ blockchain_go createwallet Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy $ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt 0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d Done! $ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10 $ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5 2017/09/12 13:08:56 ERROR: Not enough funds $ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6 00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162 Success! $ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4 $ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6 $ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0 In Bitcoin, the lock / unlock logic is stored in scripts that are stored in theScriptSigandScriptPubKeyinput and output fields, respectively. Because Bitcoin allows for different types of such scripts, it signs the entire contents of theScriptPubKey.
ScriptPubKey specified outputThis is a detailed process for handling a copy of a transaction. Most likely, it is outdated, but I could not find a more reliable source of information.
Sign method: func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { return } txCopy := tx.TrimmedCopy() for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature } } if tx.IsCoinbase() { return } txCopy := tx.TrimmedCopy() func (tx *Transaction) TrimmedCopy() Transaction { var inputs []TXInput var outputs []TXOutput for _, vin := range tx.Vin { inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} return txCopy } TXInput.Signature and TXInput.PubKey will be equal to nil. for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash Signature set to nil (just a double check), and PubKey assigned a link to the exit to PubKeyHash . At the moment, all transactions, except for the current one, are “empty”, that is, the signature and PubKey are zero. Thus, the inputs are signed separately , although this is not necessary for our application, but Bitcoin allows transactions to contain inputs that refer to different addresses. txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil Hash method serializes a transaction and hashes it using the SHA-256 algorithm. The result is data ready for signature. After receiving the hash, we have to reset the PubKey field PubKey that there is no impact on our further iterations. r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature txCopy.ID with privKey . The ECDSA signature is a pair of numbers that we combine and store in the Signature input field. func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { txCopy := tx.TrimmedCopy() curve := elliptic.P256() for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { return false } } return true } txCopy := tx.TrimmedCopy() curve := elliptic.P256() for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) TXInput.Signature and TXInput.PubKey , since the signature is a pair of numbers, and the public key is a pair of coordinates. We have concatenated them before for storage, and now we need to unpack them for use in the crypto/ecdsa . rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { return false } } return true ecdsa.PublicKey using the public key, which we take from the input, and execute ecdsa.Verify , passing the signature, from the input. If all entries are verified, we return true; if at least one entry fails validation, return false.Blockchain method: func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { if bytes.Compare(tx.ID, ID) == 0 { return *tx, nil } } if len(block.PrevBlockHash) == 0 { break } } return Transaction{}, errors.New("Transaction is not found") } func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } tx.Sign(privKey, prevTXs) } func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } return tx.Verify(prevTXs) } FindTransaction finds a transaction by identifier (this requires iteration over all blocks in the chain); SignTransaction takes one transaction, finds the transactions to which it refers, and signs it; VerifyTransaction simply verifies the transaction.NewUTXOTransaction method: func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { ... tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() bc.SignTransaction(&tx, wallet.PrivateKey) return &tx } func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte for _, tx := range transactions { if bc.VerifyTransaction(tx) != true { log.Panic("ERROR: Invalid transaction") } } ... } $ blockchain_go createwallet Your new address: 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR $ blockchain_go createwallet Your new address: 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab $ blockchain_go createblockchain -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR 000000122348da06c19e5c513710340f4c307d884385da948a205655c6a9d008 Done! $ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 6 0000000f3dbb0ab6d56c4e4b9f7479afe8d5a5dad4d2a8823345a1a16cf3347b Success! $ blockchain_go getbalance -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR Balance of '1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR': 4 $ blockchain_go getbalance -address 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab Balance of '1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab': 6 bc.SignTransaction (& tx, wallet.PrivateKey) in NewUTXOTransaction , to ensure that unsigned transactions cannot be minted: func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { ... tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() // bc.SignTransaction(&tx, wallet.PrivateKey) return &tx } $ go install $ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 1 2017/09/12 16:28:15 ERROR: Invalid transaction Source: https://habr.com/ru/post/351834/
All Articles