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 theScriptSig
andScriptPubKey
input and output fields, respectively. Because Bitcoin allows for different types of such scripts, it signs the entire contents of theScriptPubKey
.
ScriptPubKey
specified output
This 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/