Creating a Blockchain: Part 3 - Crypto key pairs & signature
creating private key, public key, signature and address
Private key and public key
Creating a crypto
package which will have all keys and signature-related functionalities.
mkdir crypto
crypto/keypair.go
package crypto
import "crypto/ecdsa"
type PrivateKey struct {
key *ecdsa.PrivateKey
}
func GeneratePrivateKey() PrivateKey{
key, err := ecdsa.GenerateKey(elliptic.P256(),rand.Reader)
if err != nil{
panic(err)
}
return PrivateKey{
key: key,
}
}
type PublicKey struct {
key *ecdsa.PublicKey
}
ECDSA stands for Elliptic Curve Digital Signature Algorithm. It is a widely used public-key cryptography algorithm that provides a method for creating digital signatures.
The GeneratePrivateKey
function in the crypto
package generates a new ECDSA private key
Address and methods
The Address
type in the types
package represents a 20-byte array commonly used as a cryptographic address.
ToSlice
method converts the Address
to a byte slice.
String
method returns the hexadecimal representation of the address.
AddressFromBytes
function constructs an Address
from a given byte slice.
types/address.go
package types
import (
"encoding/hex"
"fmt"
)
type Address [20]uint8
func (a Address) ToSlice() []byte {
b := make([]byte, 20)
for i := 0; i < 20; i++ {
b[i] = a[i]
}
return b
}
func (a Address) String() string {
return hex.EncodeToString(a.ToSlice())
}
func AddressFromBytes(b []byte) Address {
if len(b) != 20{
msg := fmt.Sprintf("given bytes with length %d should be 20",len(b))
panic(msg)
}
var value [20]uint8
for i := 0; i < 20; i++ {
value[i] = b[i]
}
return Address(value)
}
Generate keys & address
Adding more functions to keypairs,
The
PublicKey
method of thePrivateKey
struct returns the corresponding public key as aPublicKey
struct, allowing access to the public key associated with a private key.The
ToSlice
method of thePublicKey
struct converts the compressed form of the public key.The
Address
method of thePublicKey
struct generates a cryptographic address by taking the SHA-256 hash of the compressed public key and returning the last 20 bytes as an instance of thetypes.Address
type.
crypto/keypair.go
package crypto
import (
"ProjectX/types"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
)
type PrivateKey struct {
key *ecdsa.PrivateKey
}
func GeneratePrivateKey() PrivateKey{
key, err := ecdsa.GenerateKey(elliptic.P256(),rand.Reader)
if err != nil{
panic(err)
}
return PrivateKey{
key: key,
}
}
func (p PrivateKey) PublicKey() PublicKey{
return PublicKey{
key: &p.key.PublicKey,
}
}
type PublicKey struct {
key *ecdsa.PublicKey
}
func (k PublicKey) ToSlice() []byte{
return elliptic.MarshalCompressed(k.key,k.key.X,k.key.Y)
}
func (k PublicKey) Address() types.Address {
h:= sha256.Sum256(k.ToSlice())
return types.AddressFromBytes(h[len(h)-20:])
}
Test generate key
crypto/keypair_test.go
package crypto
import (
"fmt"
"testing"
)
func TestGeneratePrivateKey(t *testing.T) {
prKey := GeneratePrivateKey()
pbKey := prKey.PublicKey()
address := pbKey.Address()
fmt.Println("address",address)
}
Signature
The Signature
struct represents an ECDSA digital signature and is defined with two fields:
r
: This field is a pointer to abig.Int
represents one of the two components of the ECDSA signature.s
: This field is also a pointer to abig.Int
represents the other component of the ECDSA signature.
In the context of ECDSA (Elliptic Curve Digital Signature Algorithm), a digital signature is generated using a private key and can be verified using the corresponding public key. The signature consists of two components, r
and s
, and their combination provides cryptographic assurance of the authenticity and integrity of the signed data.
The
Sign
method generates a digital signature for the given data using the private key.The
Verify
method verifies the digital signature against the provided public key and data.
crypto/keypair.go
type Signature struct {
r,s *big.Int
}
func (k PrivateKey) Sign(data []byte) (*Signature, error) {
r,s, err := ecdsa.Sign(rand.Reader, k.key,data)
if err != nil {
return nil, err
}
return &Signature{
r:r,
s:s,
}, nil
}
func (sig Signature) Verify(pubKey PublicKey,data []byte) bool{
return ecdsa.Verify(pubKey.key, data, sig.r, sig.s)
}
Test sign and verify Signature
The
TestKeypairSignVerifySuccess
function tests the successful signing and verification of a message. It generates a private key, derives the corresponding public key, signs a message ("hello world"), and asserts that the signature is valid when verified using the public key.The
TestKeypairSignVerifyFail
function tests the failure cases for signature verification. It signs a message with one private key, and then attempts to verify the signature using a different public key and a modified message, asserting that the verification fails in both cases.
crypto/keypair_test.go
func TestKeypairSignVerifySuccess(t *testing.T) {
privKey := GeneratePrivateKey()
publicKey := privKey.PublicKey()
msg := []byte("hello world")
sig, err := privKey.Sign(msg)
assert.Nil(t, err)
assert.True(t, sig.Verify(publicKey, msg))
}
func TestKeypairSignVerifyFail(t *testing.T) {
privKey := GeneratePrivateKey()
publicKey := privKey.PublicKey()
msg := []byte("hello world")
sig, err := privKey.Sign(msg)
assert.Nil(t, err)
otherPrivKey := GeneratePrivateKey()
otherPublicKey := otherPrivKey.PublicKey()
assert.False(t, sig.Verify(otherPublicKey, msg))
assert.False(t, sig.Verify(publicKey, []byte("xxxxxx")))
}
We succeeded to sign and verify data using private key
The following blog post will explore the code related to Block and TX signing and verification✨
In this blog series, I'll be sharing code snippets related to blockchain architecture. While the code will be available on my GitHub, I want to highlight that the entire architecture isn't solely my own. I'm learning as I go, drawing inspiration and knowledge from various sources, including a helpful YouTube playlist that has contributed to my learning process.