LamportSignature.md (4811B)
1 # Lamport Signature 2 3 **Source:** MIT MAS.S62 Cryptocurrency Engineering and Design L1 4 5 **Definition:** The Lamport signature scheme is a single use signature scheme where portions of a private key are leaked to prove the private key is known by the sender of the message, as a way to sign the message. 6 7 ## Specifics 8 9 ### Key Generation 10 11 To generate a private key you generate a pair of 256 256 bit segments of random data. Since 256 bits is 32 bytes, we see the total size of the private key is 16kb (16384 bytes) 12 13 To generate the public key we perform hashing on the data from the private key. Specifically, since the sha256 hash function gives hashes that are exactly 256 bits, we apply the hash function to each of the 512 segments of 256 bits above to generate a public key where each of the resulting segments is the hash of the associated segment from the private key. It is still worthwhile to consider the public key as two keys each with 256 segments, but for the purposes of public key generation this doesn't matter. 14 15 ### Signing Messages 16 17 To sign a message we first hash the message using the sha256 hash function. This gives us 256 bits of data to work with. 18 19 Using these 256 bits of data, we perform the operations specified in the below pseudocode: 20 21 ``` 22 23 signature = "" 24 for idx in range(hash) 25 if hash[idx] == 0: 26 signature += privateKey.first[idx] 27 if hash[idx] == 1: 28 signature += privateKey.second[idx] 29 return signature 30 31 ``` 32 33 The signature is thus exactly half of the private key where the half revealed is (generally) some portion of the first half and some portion second half of the private key, based on value of the current bit from the message hash. 34 35 ### Verifying Messages 36 37 To verify a message we iterate over each of the bits from the message's hash and verify the segments revealed as the private key hash to the associated segments of the public key. 38 39 ``` 40 for idx in range(hash): 41 if hash[idx] == 0: 42 assert sha256sum(privateKey.first[idx]) == publicKey[idx] 43 if hash[idx] == 1: 44 assert sha256sum(privateKey.second[idx]) == publicKey[idx] 45 ``` 46 47 ## Limitations 48 49 - The keys for lamport signatures, along with the signatures themselves, are large 50 - public / private keys are 16kb 51 - signatures are 8kb 52 - Keys should only be used once 53 - One iteration leaks half of the key 54 - Two iterations leaks ~3/4 of the key 55 - Depending on the message being signed, even more segments may be revealed 56 - ... 57 58 ## Benefits 59 60 - Simple to implement 61 - Cheap to compute 62 - No large primes 63 - Quantum resistant 64 - Impacted by grover's algorithm so expect 128 bits of security instead of 256 when facing quantum computers 65 - This is generally considered acceptable 66 67 ## Implementation 68 69 ```go 70 71 package main 72 73 import ( 74 "crypto/rand" 75 "crypto/sha256" 76 "fmt" 77 ) 78 79 type Key struct { 80 firstRow [256][32]byte 81 secondRow [256][32]byte 82 } 83 84 func GenerateKeys() (Key, Key) { 85 86 privateKey := Key{} 87 88 for i := range 256 { 89 _, err := rand.Read(privateKey.firstRow[i][:]) 90 if err != nil { 91 panic(err) 92 } 93 } 94 95 for i := range 256 { 96 _, err := rand.Read(privateKey.secondRow[i][:]) 97 if err != nil { 98 panic(err) 99 } 100 } 101 102 publicKey := Key{} 103 104 for i := range 256 { 105 publicKey.firstRow[i] = sha256.Sum256(privateKey.firstRow[i][:]) 106 } 107 108 for i := range 256 { 109 publicKey.secondRow[i] = sha256.Sum256(privateKey.secondRow[i][:]) 110 111 } 112 113 return publicKey, privateKey 114 } 115 116 func Sign(privateKey Key, message string) [256][32]byte { 117 118 messageHash := sha256.Sum256([]byte(message)) 119 120 signature := [256][32]byte{} 121 122 for index := range 32 { 123 currentByteString := fmt.Sprintf("%08b", messageHash[index]) 124 for idx := range 8 { 125 currentBit := currentByteString[idx] 126 if currentBit == '0' { 127 signature[(index*8)+idx] = privateKey.firstRow[(index*8)+idx] 128 } else { 129 signature[(index*8)+idx] = privateKey.secondRow[(index*8)+idx] 130 } 131 } 132 } 133 134 return signature 135 } 136 137 func Verify(publicKey Key, message string, signature [256][32]byte) bool { 138 139 messageHash := sha256.Sum256([]byte(message)) 140 141 for index := range 32 { 142 currentByteString := fmt.Sprintf("%08b", messageHash[index]) 143 for idx := range 8 { 144 currentBit := currentByteString[idx] 145 if currentBit == '0' { 146 // signature[(index * 8) + idx] = privateKey.firstRow[(index * 8) + idx] 147 if sha256.Sum256(signature[(index*8)+idx][:]) != publicKey.firstRow[(index*8)+idx] { 148 return false 149 } 150 } else { 151 if sha256.Sum256(signature[(index*8)+idx][:]) != publicKey.secondRow[(index*8)+idx] { 152 return false 153 } 154 } 155 } 156 } 157 158 return true 159 160 } 161 162 func main() { 163 164 publicKey, privateKey := GenerateKeys() 165 message := rand.Text() 166 signature := Sign(privateKey, message) 167 valid := Verify(publicKey, message, signature) 168 fmt.Printf("Verified message matches signature: %v\n", valid) 169 170 } 171 ```