mit-ocw

Source code for MIT-OCW coursework
git clone git://git.laack.co/mit-ocw.git
Log | Files | Refs

commit 1cacbb40d5c286bb8ed136da2a728e84703acb52
parent a7a70c75b0fb0e1d6832fd65e9590aa83a491fa0
Author: Andrew Laack <andrew@laack.co>
Date:   Sat, 16 May 2026 00:31:20 -0500

Implemented Lamport signature scheme in go

Diffstat:
Amas.s62-cryptocurrency-engineering-and-design/lamport-signature/go.mod | 3+++
Amas.s62-cryptocurrency-engineering-and-design/lamport-signature/main.go | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amas.s62-cryptocurrency-engineering-and-design/lamport-signature/main_test.go | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 238 insertions(+), 0 deletions(-)

diff --git a/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/go.mod b/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/go.mod @@ -0,0 +1,3 @@ +module lamportsignature + +go 1.26.3 diff --git a/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/main.go b/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "crypto/rand" + "crypto/sha256" + "fmt" +) + +// 1. publicKey, privateKey := GenerateKeys(randomSeed) +// 2. signature := Sign(privateKey, message) +// 3. valid := Verify(publicKey, message, signature) + + +// 256 blocks of 256 bits (32 bytes) +// spread across two rows + +type Key struct { + firstRow [256][32] byte + secondRow[256][32] byte +} + + +func GenerateKeys () (Key, Key){ + + privateKey := Key{} + + for i := range 256 { + _, err := rand.Read(privateKey.firstRow[i][:]) + if err != nil { + panic(err) + } + } + + for i := range 256 { + _, err := rand.Read(privateKey.secondRow[i][:]) + if err != nil { + panic(err) + } + } + + + publicKey := Key{} + + for i := range 256 { + publicKey.firstRow[i] = sha256.Sum256(privateKey.firstRow[i][:]) + } + + for i := range 256 { + publicKey.secondRow[i] = sha256.Sum256(privateKey.secondRow[i][:]) + + } + + return publicKey, privateKey +} + +func Sign(privateKey Key, message string) [256][32]byte { + + messageHash := sha256.Sum256([]byte(message)) + + signature := [256][32]byte{} + + for index := range 32 { + currentByteString := fmt.Sprintf("%08b", messageHash[index]) + for idx := range 8 { + currentBit := currentByteString[idx] + if currentBit == '1' { + signature[(index * 8) + idx] = privateKey.firstRow[(index * 8) + idx] + } else { + signature[(index * 8) + idx] = privateKey.secondRow[(index * 8) + idx] + } + } + } + + return signature +} + + +func Verify(publicKey Key, message string, signature [256][32]byte) bool { + + messageHash := sha256.Sum256([]byte(message)) + + for index := range 32 { + currentByteString := fmt.Sprintf("%08b", messageHash[index]) + for idx := range 8 { + currentBit := currentByteString[idx] + if currentBit == '1' { + // signature[(index * 8) + idx] = privateKey.firstRow[(index * 8) + idx] + if sha256.Sum256(signature[(index * 8) + idx][:]) != publicKey.firstRow[(index * 8) + idx] { + return false + } + } else { + if sha256.Sum256(signature[(index * 8) + idx][:]) != publicKey.secondRow[(index * 8) + idx] { + return false + } + } + } + } + + return true + +} + +func main() { + + // Generally, you'd pass a random seed to generatekeys, but crypto/rand.Read + // doesn't accept a seed. + + publicKey, privateKey := GenerateKeys() + + message := rand.Text() + + signature := Sign(privateKey, message) + + + valid := Verify(publicKey, message, signature) + + fmt.Printf("Verified message matches signature: %v\n", valid) + +} diff --git a/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/main_test.go b/mas.s62-cryptocurrency-engineering-and-design/lamport-signature/main_test.go @@ -0,0 +1,116 @@ +// These are all property based tests. + +// These don't validate the implementation against a lamport signature spec, but +// they do validate expected invariants for signatures, messages, and keys. + +package main + +import ( + "crypto/rand" + "sync" + "testing" +) + +func signAndValidate(t *testing.T, wg *sync.WaitGroup) { + + defer wg.Done() + + publicKey, privateKey := GenerateKeys() + + message := rand.Text() + + signature := Sign(privateKey, message) + + + valid := Verify(publicKey, message, signature) + + if valid != true { + t.Errorf("Signature generation and verification mismatch.") + } +} + + +func signChangeAndValidate(t *testing.T, wg *sync.WaitGroup) { + + defer wg.Done() + + publicKey, privateKey := GenerateKeys() + + message := rand.Text() + + signature := Sign(privateKey, message) + + signature[0][0] += 1 + + valid := Verify(publicKey, message, signature) + + if valid != false { + t.Errorf("Signature changed, but still verified") + } +} + +func signDifferentMessagesAndValidate(t *testing.T, wg *sync.WaitGroup) { + + defer wg.Done() + + publicKey, privateKey := GenerateKeys() + + message1 := rand.Text() + message2 := rand.Text() + + signature := Sign(privateKey, message1) + + valid := Verify(publicKey, message1, signature) + + if valid != true { + t.Errorf("Unable to validate signature for message it was generated with.") + } + + valid = Verify(publicKey, message2, signature) + + if valid != false { + t.Errorf("Signature valid for multiple messages") + } +} + +func TestSignatureIsValid(t *testing.T) { + + var wg sync.WaitGroup + + for _ = range 100000 { + wg.Add(1) + go signAndValidate(t, &wg) + } + + wg.Wait() + + +} + +func TestSignatureChangeInvalidation(t *testing.T) { + + var wg sync.WaitGroup + + for _ = range 100000 { + wg.Add(1) + go signChangeAndValidate(t, &wg) + } + + wg.Wait() + + +} + +func TestMessageChangeInvalidation(t *testing.T) { + + var wg sync.WaitGroup + + for _ = range 100000 { + wg.Add(1) + go signDifferentMessagesAndValidate(t, &wg) + } + + wg.Wait() + + +}