├── .travis.yml ├── LICENSE ├── README.md ├── cases_test.go ├── go.mod ├── go.sum ├── schnorr.go └── schnorr_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.11.x" 4 | 5 | env: 6 | - GO111MODULE=on 7 | 8 | install: true 9 | 10 | script: go test -v ./... 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bakhtiyor Homidov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/hbakhtiyor/schnorr?status.svg)](https://godoc.org/github.com/hbakhtiyor/schnorr) [![Build Status](https://travis-ci.com/hbakhtiyor/schnorr.svg?branch=master)](https://travis-ci.com/hbakhtiyor/schnorr) [![Go Report Card](https://goreportcard.com/badge/github.com/hbakhtiyor/schnorr)](https://goreportcard.com/report/github.com/hbakhtiyor/schnorr) [![License](https://badges.fyi/github/license/hbakhtiyor/schnorr)](https://github.com/hbakhtiyor/schnorr/blob/master/LICENSE) [![Latest tag](https://badges.fyi/github/latest-tag/hbakhtiyor/schnorr)](https://github.com/hbakhtiyor/schnorr/releases) 2 | 3 | Go implementation of the Schnorr BIP 4 | ================= 5 | 6 | This is a Go implementation of the standard 64-byte Schnorr signature 7 | scheme over the elliptic curve *secp256k1*. 8 | 9 | The code is based upon the 10 | [initial proposal of Pieter Wuille](https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki) 11 | when it didn't have a BIP number assigned yet. 12 | 13 | The current version passes all test vectors provided 14 | [here](https://raw.githubusercontent.com/sipa/bips/bip-schnorr/bip-schnorr/test-vectors.csv). 15 | **But the author does not give any guarantees that the algorithm is implemented 16 | correctly for every edge case!** 17 | 18 | ## Table of Contents 19 | 20 | * [Usage](#usage) 21 | * [API](#api) 22 | * [Sign(privateKey *big.Int, message [32]byte) ([64]byte, error)](#signprivatekey-bigint-message-32byte-64byte-error) 23 | * [Arguments](#arguments) 24 | * [Returns](#returns) 25 | * [Examples](#examples) 26 | * [Verify(publicKey [33]byte, message [32]byte, signature [64]byte) (bool, error)](#verifypublickey-33byte-message-32byte-signature-64byte-bool-error) 27 | * [Arguments](#arguments-1) 28 | * [Returns](#returns-1) 29 | * [Examples](#examples-1) 30 | * [BatchVerify(publicKeys [][33]byte, messages [][32]byte, signatures [][64]byte) (bool, error)](#batchverifypublickeys-33byte-messages-32byte-signatures-64byte-bool-error) 31 | * [Arguments](#arguments-2) 32 | * [Returns](#returns-2) 33 | * [Examples](#examples-2) 34 | * [AggregateSignatures(privateKeys []*big.Int, message [32]byte) ([64]byte, error)](#aggregatesignaturesprivatekeys-bigint-message-32byte-64byte-error) 35 | * [Arguments](#arguments-3) 36 | * [Returns](#returns-3) 37 | * [Examples](#examples-3) 38 | * [Benchmark](#benchmark) 39 | * [Hardware used](#hardware-used) 40 | * [Version](#version) 41 | * [Credit](#credit) 42 | 43 | 44 | ## Usage 45 | Install using: 46 | 47 | ```shell 48 | go get -u github.com/hbakhtiyor/schnorr 49 | ``` 50 | 51 | In your code: 52 | 53 | ```go 54 | import "github.com/hbakhtiyor/schnorr" 55 | 56 | signature, err := schnorr.Sign(privateKey, message) 57 | 58 | result, err := schnorr.Verify(publicKey, message, signature) 59 | 60 | result, err := schnorr.BatchVerify(publicKeys, messages, signatures) 61 | 62 | signature, err := schnorr.AggregateSignatures(privateKeys, message) 63 | ``` 64 | ## API 65 | 66 | Requiring the module gives an object with four methods: 67 | 68 | ### Sign(privateKey *big.Int, message [32]byte) ([64]byte, error) 69 | 70 | Sign a 32-byte message with the private key, returning a 64-byte signature. Read [more](https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#signing) 71 | 72 | ##### Arguments 73 | 74 | 1. privateKey (*big.Int): The integer secret key in the range 1..n-1. 75 | 2. message ([32]byte): The 32-byte array message. 76 | 77 | ##### Returns 78 | 79 | ([64]byte, error): A 64-byte array signature. An error if signing fails. 80 | 81 | ##### Examples 82 | 83 | ```go 84 | var message [32]byte 85 | 86 | privateKey, _ := new(big.Int).SetString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", 16) 87 | msg, _ := hex.DecodeString("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") 88 | copy(message[:], msg) 89 | 90 | signature, err := schnorr.Sign(privateKey, message) 91 | if err != nil { 92 | fmt.Printf("The signing is failed: %v\n", err) 93 | } 94 | fmt.Printf("The signature is: %x\n", signature) 95 | ``` 96 | 97 | ### Verify(publicKey [33]byte, message [32]byte, signature [64]byte) (bool, error) 98 | 99 | Verify a 64-byte signature of a 32-byte message against the public key. Read [more](https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#verification) 100 | 101 | ##### Arguments 102 | 103 | 1. publicKey ([33]byte): The 33-byte array public key. 104 | 2. message ([32]byte): The 32-byte array message. 105 | 3. signature ([64]byte): The 64-byte array signature. 106 | 107 | ##### Returns 108 | 109 | (bool, error): True if signature is valid, An error if verification fails. 110 | 111 | ##### Examples 112 | 113 | ```go 114 | var ( 115 | publicKey [33]byte 116 | message [32]byte 117 | signature [64]byte 118 | ) 119 | 120 | pk, _ := hex.DecodeString("02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659") 121 | copy(publicKey[:], pk) 122 | msg, _ := hex.DecodeString("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") 123 | copy(message[:], msg) 124 | sig, _ := hex.DecodeString("2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD") 125 | copy(signature[:], sig) 126 | 127 | if result, err := schnorr.Verify(publicKey, message, signature); err != nil { 128 | fmt.Printf("The signature verification failed: %v\n", err) 129 | } else if result { 130 | fmt.Println("The signature is valid.") 131 | } 132 | ``` 133 | 134 | ### BatchVerify(publicKeys [][33]byte, messages [][32]byte, signatures [][64]byte) (bool, error) 135 | 136 | Verify a list of 64-byte signatures of a 32-byte messages against the public keys. Read [more](https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#batch-verification) 137 | 138 | ##### Arguments 139 | 140 | 1. publicKeys ([][33]byte): The list of 33-byte array public keys. 141 | 2. messages ([][32]byte): The list of 32-byte array messages. 142 | 3. signatures ([][64]byte): The list of 64-byte array signatures. 143 | 144 | ##### Returns 145 | 146 | (bool, error): True if all signatures are valid, An error if one verification fails. 147 | 148 | ##### Examples 149 | 150 | ```go 151 | var ( 152 | publicKey [33]byte 153 | message [32]byte 154 | signature [64]byte 155 | publicKeys [][33]byte 156 | messages [][32]byte 157 | signatures [][64]byte 158 | ) 159 | 160 | pk, _ := hex.DecodeString("02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659") 161 | copy(publicKey[:], pk) 162 | publicKeys = append(publicKeys, publicKey) 163 | pk, _ = hex.DecodeString("03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B") 164 | copy(publicKey[:], pk) 165 | publicKeys = append(publicKeys, publicKey) 166 | pk, _ = hex.DecodeString("026D7F1D87AB3BBC8BC01F95D9AECE1E659D6E33C880F8EFA65FACF83E698BBBF7") 167 | copy(publicKey[:], pk) 168 | publicKeys = append(publicKeys, publicKey) 169 | msg, _ := hex.DecodeString("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") 170 | copy(message[:], msg) 171 | messages = append(messages, message) 172 | msg, _ = hex.DecodeString("5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") 173 | copy(message[:], msg) 174 | messages = append(messages, message) 175 | msg, _ = hex.DecodeString("B2F0CD8ECB23C1710903F872C31B0FD37E15224AF457722A87C5E0C7F50FFFB3") 176 | copy(message[:], msg) 177 | messages = append(messages, message) 178 | sig, _ := hex.DecodeString("2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD") 179 | copy(signature[:], sig) 180 | signatures = append(signatures, signature) 181 | sig, _ = hex.DecodeString("00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380") 182 | copy(signature[:], sig) 183 | signatures = append(signatures, signature) 184 | sig, _ = hex.DecodeString("68CA1CC46F291A385E7C255562068357F964532300BEADFFB72DD93668C0C1CAC8D26132EB3200B86D66DE9C661A464C6B2293BB9A9F5B966E53CA736C7E504F") 185 | copy(signature[:], sig) 186 | signatures = append(signatures, signature) 187 | 188 | if result, err := schnorr.BatchVerify(publicKeys, messages, signatures); err != nil { 189 | fmt.Printf("The signature verification failed: %v\n", err) 190 | } else if result { 191 | fmt.Println("The signature is valid.") 192 | } 193 | ``` 194 | 195 | ### AggregateSignatures(privateKeys []*big.Int, message [32]byte) ([64]byte, error) 196 | 197 | Aggregate multiple signatures of different private keys over the same message into a single 64-byte signature. 198 | 199 | ##### Arguments 200 | 201 | 1. privateKeys ([]*big.Int): The list of integer secret keys in the range 1..n-1. 202 | 2. message ([32]byte): The list of 32-byte array messages. 203 | 204 | ##### Returns 205 | 206 | (bool, error): True if all signatures are valid, An error if one verification fails. 207 | ([64]byte, error): A 64-byte array signature. An error if signing fails. 208 | 209 | ##### Examples 210 | 211 | ```go 212 | var ( 213 | publicKey [33]byte 214 | message [32]byte 215 | ) 216 | 217 | privateKey1, _ := new(big.Int).SetString("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", 16) 218 | privateKey2, _ := new(big.Int).SetString("C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7", 16) 219 | msg, _ := hex.DecodeString("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") 220 | copy(message[:], msg) 221 | 222 | privateKeys := []*big.Int{privateKey1, privateKey2} 223 | signature, _ := schnorr.AggregateSignatures(privateKeys, message) 224 | 225 | // verifying an aggregated signature 226 | pk, _ := hex.DecodeString("02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659") 227 | copy(publicKey[:], pk) 228 | P1x, P1y := schnorr.Unmarshal(Curve, publicKey[:]) 229 | 230 | pk, _ = hex.DecodeString("03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B") 231 | copy(publicKey[:], pk) 232 | P2x, P2y := schnorr.Unmarshal(Curve, publicKey[:]) 233 | Px, Py := Curve.Add(P1x, P1y, P2x, P2y) 234 | 235 | copy(publicKey[:], schnorr.Marshal(Curve, Px, Py)) 236 | 237 | if result, err := schnorr.Verify(publicKey, message, signature); err != nil { 238 | fmt.Printf("The signature verification failed: %v\n", err) 239 | } else if result { 240 | fmt.Println("The signature is valid.") 241 | } 242 | ``` 243 | 244 | ## Benchmarks 245 | 246 | ``` 247 | BenchmarkSign-4 2000 1015337 ns/op 45812 B/op 814 allocs/op 248 | BenchmarkVerify-4 200 8555659 ns/op 217884 B/op 3622 allocs/op 249 | BenchmarkBatchVerify-4 100 12114966 ns/op 148343 B/op 2220 allocs/op 250 | BenchmarkAggregateSignatures-4 2000 593665 ns/op 21981 B/op 400 allocs/op 251 | ``` 252 | 253 | ##### Hardware used 254 | 255 | * Intel® Core™ i3-2310M CPU @ 2.10GHz × 4 256 | * 4Gb RAM 257 | 258 | ##### Versions 259 | 260 | * Go 1.11.2 261 | * Ubuntu 18.04.01 LTS x86_64 OS 262 | * 4.15.0-39-generic kernel 263 | 264 | ## Credits 265 | 266 | * https://github.com/guggero/bip-schnorr 267 | * https://github.com/sipa/bips/tree/bip-schnorr/bip-schnorr 268 | -------------------------------------------------------------------------------- /cases_test.go: -------------------------------------------------------------------------------- 1 | package schnorr 2 | 3 | import "errors" 4 | 5 | var testCases = []struct { 6 | d string 7 | pk string 8 | m string 9 | sig string 10 | result bool 11 | err error 12 | description string 13 | }{ 14 | { 15 | "0000000000000000000000000000000000000000000000000000000000000001", 16 | "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 17 | "0000000000000000000000000000000000000000000000000000000000000000", 18 | "787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF67031A98831859DC34DFFEEDDA86831842CCD0079E1F92AF177F7F22CC1DCED05", 19 | true, 20 | nil, 21 | "", 22 | }, 23 | { 24 | "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", 25 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 26 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 27 | "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", 28 | true, 29 | nil, 30 | "", 31 | }, 32 | { 33 | "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7", 34 | "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", 35 | "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", 36 | "00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380", 37 | true, 38 | nil, 39 | "", 40 | }, 41 | { 42 | "6d6c66873739bc7bfb3526629670d0ea357e92cc4581490d62779ae15f6b787b", 43 | "026d7f1d87ab3bbc8bc01f95d9aece1e659d6e33c880f8efa65facf83e698bbbf7", 44 | "b2f0cd8ecb23c1710903f872c31b0fd37e15224af457722a87c5e0c7f50fffb3", 45 | "68ca1cc46f291a385e7c255562068357f964532300beadffb72dd93668c0c1cac8d26132eb3200b86d66de9c661a464c6b2293bb9a9f5b966e53ca736c7e504f", 46 | true, 47 | nil, 48 | "", 49 | }, 50 | { 51 | "", 52 | "03DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", 53 | "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", 54 | "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6302A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D", 55 | true, 56 | nil, 57 | "", 58 | }, 59 | { 60 | "", 61 | "031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F", 62 | "0000000000000000000000000000000000000000000000000000000000000000", 63 | "52818579ACA59767E3291D91B76B637BEF062083284992F2D95F564CA6CB4E3530B1DA849C8E8304ADC0CFE870660334B3CFC18E825EF1DB34CFAE3DFC5D8187", 64 | true, 65 | nil, 66 | "test fails if jacobi symbol of x(R) instead of y(R) is used", 67 | }, 68 | { 69 | "", 70 | "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", 71 | "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 72 | "570DD4CA83D4E6317B8EE6BAE83467A1BF419D0767122DE409394414B05080DCE9EE5F237CBD108EABAE1E37759AE47F8E4203DA3532EB28DB860F33D62D49BD", 73 | true, 74 | nil, 75 | "test fails if msg is reduced", 76 | }, 77 | { 78 | "", 79 | "03EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", 80 | "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", 81 | "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6302A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D", 82 | false, 83 | errors.New("signature verification failed"), 84 | "public key not on the curve", 85 | }, 86 | { 87 | "", 88 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 89 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 90 | "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1DFA16AEE06609280A19B67A24E1977E4697712B5FD2943914ECD5F730901B4AB7", 91 | false, 92 | errors.New("signature verification failed"), 93 | "incorrect R residuosity", 94 | }, 95 | { 96 | "", 97 | "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", 98 | "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", 99 | "00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BED092F9D860F1776A1F7412AD8A1EB50DACCC222BC8C0E26B2056DF2F273EFDEC", 100 | false, 101 | errors.New("signature verification failed"), 102 | "negated message hash", 103 | }, 104 | { 105 | "", 106 | "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 107 | "0000000000000000000000000000000000000000000000000000000000000000", 108 | "787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF68FCE5677CE7A623CB20011225797CE7A8DE1DC6CCD4F754A47DA6C600E59543C", 109 | false, 110 | errors.New("signature verification failed"), 111 | "negated s value", 112 | }, 113 | { 114 | "", 115 | "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 116 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 117 | "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", 118 | false, 119 | errors.New("signature verification failed"), 120 | "negated public key", 121 | }, 122 | { 123 | "", 124 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 125 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 126 | "00000000000000000000000000000000000000000000000000000000000000009E9D01AF988B5CEDCE47221BFA9B222721F3FA408915444A4B489021DB55775F", 127 | false, 128 | errors.New("signature verification failed"), 129 | "sG - eP is infinite. Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 0", 130 | }, 131 | { 132 | "", 133 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 134 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 135 | "0000000000000000000000000000000000000000000000000000000000000001D37DDF0254351836D84B1BD6A795FD5D523048F298C4214D187FE4892947F728", 136 | false, 137 | errors.New("signature verification failed"), 138 | "sG - eP is infinite. Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 1", 139 | }, 140 | { 141 | "", 142 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 143 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 144 | "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", 145 | false, 146 | errors.New("signature verification failed"), 147 | "sig[0:32] is not an X coordinate on the curve", 148 | }, 149 | { 150 | "", 151 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 152 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 153 | "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2F1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", 154 | false, 155 | errors.New("r is larger than or equal to field size"), 156 | "sig[0:32] is equal to field size", 157 | }, 158 | { 159 | "", 160 | "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", 161 | "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", 162 | "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 163 | false, 164 | errors.New("s is larger than or equal to curve order"), 165 | "sig[32:64] is equal to curve order", 166 | }, 167 | { 168 | "", 169 | "6d6c66873739bc7bfb3526629670d0ea", 170 | "b2f0cd8ecb23c1710903f872c31b0fd37e15224af457722a87c5e0c7f50fffb3", 171 | "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", 172 | false, 173 | errors.New("signature verification failed"), 174 | "public key is only 16 bytes", 175 | }, 176 | } 177 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hbakhtiyor/schnorr 2 | 3 | require ( 4 | github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95 5 | github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589 // indirect 6 | github.com/btcsuite/goleveldb v1.0.0 // indirect 7 | github.com/davecgh/go-spew v1.1.1 // indirect 8 | github.com/jessevdk/go-flags v1.4.0 // indirect 9 | github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect 10 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect 11 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 12 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect 13 | golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb // indirect 14 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 15 | gopkg.in/yaml.v2 v2.2.2 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 2 | github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95 h1:bmv+LE3sbjb/M06u2DBi92imeKj7KnCUBOvyZYqI8d8= 3 | github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= 4 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 5 | github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 6 | github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 7 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 8 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 9 | github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= 10 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 11 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 12 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 13 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 14 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 17 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 18 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 19 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 20 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 21 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 22 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 23 | github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 24 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 25 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 26 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 27 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 28 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 29 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 30 | golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 31 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 32 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 33 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 40 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 41 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 42 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | -------------------------------------------------------------------------------- /schnorr.go: -------------------------------------------------------------------------------- 1 | package schnorr 2 | 3 | import ( 4 | "crypto/elliptic" 5 | "crypto/rand" 6 | "crypto/sha256" 7 | "errors" 8 | "math/big" 9 | 10 | "github.com/btcsuite/btcd/btcec" 11 | ) 12 | 13 | var ( 14 | // Curve is a KoblitzCurve which implements secp256k1. 15 | Curve = btcec.S256() 16 | // One holds a big integer of 1 17 | One = new(big.Int).SetInt64(1) 18 | // Two holds a big integer of 2 19 | Two = new(big.Int).SetInt64(2) 20 | // Three holds a big integer of 3 21 | Three = new(big.Int).SetInt64(3) 22 | // Four holds a big integer of 4 23 | Four = new(big.Int).SetInt64(4) 24 | // Seven holds a big integer of 7 25 | Seven = new(big.Int).SetInt64(7) 26 | // N2 holds a big integer of N-2 27 | N2 = new(big.Int).Sub(Curve.N, Two) 28 | ) 29 | 30 | // Sign a 32 byte message with the private key, returning a 64 byte signature. 31 | // https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#signing 32 | func Sign(privateKey *big.Int, message [32]byte) ([64]byte, error) { 33 | sig := [64]byte{} 34 | if privateKey.Cmp(One) < 0 || privateKey.Cmp(new(big.Int).Sub(Curve.N, One)) > 0 { 35 | return sig, errors.New("the private key must be an integer in the range 1..n-1") 36 | } 37 | 38 | d := intToByte(privateKey) 39 | k0, err := deterministicGetK0(d, message) 40 | if err != nil { 41 | return sig, err 42 | } 43 | 44 | Rx, Ry := Curve.ScalarBaseMult(intToByte(k0)) 45 | k := getK(Ry, k0) 46 | 47 | Px, Py := Curve.ScalarBaseMult(d) 48 | rX := intToByte(Rx) 49 | e := getE(Px, Py, rX, message) 50 | e.Mul(e, privateKey) 51 | k.Add(k, e) 52 | k.Mod(k, Curve.N) 53 | 54 | copy(sig[:32], rX) 55 | copy(sig[32:], intToByte(k)) 56 | return sig, nil 57 | } 58 | 59 | // Verify a 64 byte signature of a 32 byte message against the public key. 60 | // Returns an error if verification fails. 61 | // https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#verification 62 | func Verify(publicKey [33]byte, message [32]byte, signature [64]byte) (bool, error) { 63 | Px, Py := Unmarshal(Curve, publicKey[:]) 64 | 65 | if Px == nil || Py == nil || !Curve.IsOnCurve(Px, Py) { 66 | return false, errors.New("signature verification failed") 67 | } 68 | r := new(big.Int).SetBytes(signature[:32]) 69 | if r.Cmp(Curve.P) >= 0 { 70 | return false, errors.New("r is larger than or equal to field size") 71 | } 72 | s := new(big.Int).SetBytes(signature[32:]) 73 | if s.Cmp(Curve.N) >= 0 { 74 | return false, errors.New("s is larger than or equal to curve order") 75 | } 76 | 77 | e := getE(Px, Py, intToByte(r), message) 78 | sGx, sGy := Curve.ScalarBaseMult(intToByte(s)) 79 | // e.Sub(Curve.N, e) 80 | ePx, ePy := Curve.ScalarMult(Px, Py, intToByte(e)) 81 | ePy.Sub(Curve.P, ePy) 82 | Rx, Ry := Curve.Add(sGx, sGy, ePx, ePy) 83 | 84 | if (Rx.Sign() == 0 && Ry.Sign() == 0) || big.Jacobi(Ry, Curve.P) != 1 || Rx.Cmp(r) != 0 { 85 | return false, errors.New("signature verification failed") 86 | } 87 | return true, nil 88 | } 89 | 90 | // BatchVerify verifies a list of 64 byte signatures of 32 byte messages against the public keys. 91 | // Returns an error if verification fails. 92 | // https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#batch-verification 93 | func BatchVerify(publicKeys [][33]byte, messages [][32]byte, signatures [][64]byte) (bool, error) { 94 | if publicKeys == nil || len(publicKeys) == 0 { 95 | return false, errors.New("publicKeys must be an array with one or more elements") 96 | } 97 | if messages == nil || len(messages) == 0 { 98 | return false, errors.New("messages must be an array with one or more elements") 99 | } 100 | if signatures == nil || len(signatures) == 0 { 101 | return false, errors.New("signatures must be an array with one or more elements") 102 | } 103 | if len(publicKeys) != len(messages) || len(messages) != len(signatures) { 104 | return false, errors.New("all parameters must be an array with the same length") 105 | } 106 | 107 | ls := new(big.Int).SetInt64(0) 108 | a := new(big.Int).SetInt64(1) 109 | rsx, rsy := new(big.Int), new(big.Int) 110 | 111 | for i, signature := range signatures { 112 | publicKey := publicKeys[i] 113 | message := messages[i] 114 | Px, Py := Unmarshal(Curve, publicKey[:]) 115 | 116 | if Px == nil || Py == nil || !Curve.IsOnCurve(Px, Py) { 117 | return false, errors.New("signature verification failed") 118 | } 119 | r := new(big.Int).SetBytes(signature[:32]) 120 | if r.Cmp(Curve.P) >= 0 { 121 | return false, errors.New("r is larger than or equal to field size") 122 | } 123 | s := new(big.Int).SetBytes(signature[32:]) 124 | if s.Cmp(Curve.N) >= 0 { 125 | return false, errors.New("s is larger than or equal to curve order") 126 | } 127 | 128 | e := getE(Px, Py, intToByte(r), message) 129 | 130 | r2 := new(big.Int).Exp(r, Three, nil) 131 | r2.Add(r2, Seven) 132 | c := r2.Mod(r2, Curve.P) 133 | exp := new(big.Int).Add(Curve.P, One) 134 | exp.Div(exp, Four) 135 | 136 | y := new(big.Int).Exp(c, exp, Curve.P) 137 | 138 | if new(big.Int).Exp(y, Two, Curve.P).Cmp(c) != 0 { 139 | return false, errors.New("signature verification failed") 140 | } 141 | 142 | Rx, Ry := r, y 143 | 144 | if i != 0 { 145 | var err error 146 | a, err = deterministicGetRandA() 147 | if err != nil { 148 | return false, err 149 | } 150 | } 151 | 152 | aRx, aRy := Curve.ScalarMult(Rx, Ry, intToByte(a)) 153 | aePx, aePy := Curve.ScalarMult(Px, Py, e.Mul(e, a).Bytes()) 154 | rsx, rsy = Curve.Add(rsx, rsy, aRx, aRy) 155 | rsx, rsy = Curve.Add(rsx, rsy, aePx, aePy) 156 | s.Mul(s, a) 157 | ls.Add(ls, s) 158 | } 159 | 160 | Gx, Gy := Curve.ScalarBaseMult(intToByte(ls.Mod(ls, Curve.N))) 161 | if Gx.Cmp(rsx) != 0 || Gy.Cmp(rsy) != 0 { 162 | return false, errors.New("signature verification failed") 163 | } 164 | 165 | return true, nil 166 | } 167 | 168 | // AggregateSignatures aggregates multiple signatures of different private keys over 169 | // the same message into a single 64 byte signature. 170 | func AggregateSignatures(privateKeys []*big.Int, message [32]byte) ([64]byte, error) { 171 | sig := [64]byte{} 172 | if privateKeys == nil || len(privateKeys) == 0 { 173 | return sig, errors.New("privateKeys must be an array with one or more elements") 174 | } 175 | 176 | k0s := []*big.Int{} 177 | Px, Py := new(big.Int), new(big.Int) 178 | Rx, Ry := new(big.Int), new(big.Int) 179 | for _, privateKey := range privateKeys { 180 | if privateKey.Cmp(One) < 0 || privateKey.Cmp(new(big.Int).Sub(Curve.N, One)) > 0 { 181 | return sig, errors.New("the private key must be an integer in the range 1..n-1") 182 | } 183 | 184 | d := intToByte(privateKey) 185 | k0i, err := deterministicGetK0(d, message) 186 | if err != nil { 187 | return sig, err 188 | } 189 | 190 | RiX, RiY := Curve.ScalarBaseMult(intToByte(k0i)) 191 | PiX, PiY := Curve.ScalarBaseMult(d) 192 | 193 | k0s = append(k0s, k0i) 194 | 195 | Rx, Ry = Curve.Add(Rx, Ry, RiX, RiY) 196 | Px, Py = Curve.Add(Px, Py, PiX, PiY) 197 | } 198 | 199 | rX := intToByte(Rx) 200 | e := getE(Px, Py, rX, message) 201 | s := new(big.Int).SetInt64(0) 202 | 203 | for i, k0 := range k0s { 204 | k := getK(Ry, k0) 205 | k.Add(k, new(big.Int).Mul(e, privateKeys[i])) 206 | s.Add(s, k) 207 | } 208 | 209 | copy(sig[:32], rX) 210 | copy(sig[32:], intToByte(s.Mod(s, Curve.N))) 211 | return sig, nil 212 | } 213 | 214 | func getE(Px, Py *big.Int, rX []byte, m [32]byte) *big.Int { 215 | r := append(rX, Marshal(Curve, Px, Py)...) 216 | r = append(r, m[:]...) 217 | h := sha256.Sum256(r) 218 | i := new(big.Int).SetBytes(h[:]) 219 | return i.Mod(i, Curve.N) 220 | } 221 | 222 | func getK(Ry, k0 *big.Int) *big.Int { 223 | if big.Jacobi(Ry, Curve.P) == 1 { 224 | return k0 225 | } 226 | return k0.Sub(Curve.N, k0) 227 | } 228 | 229 | func deterministicGetK0(d []byte, message [32]byte) (*big.Int, error) { 230 | h := sha256.Sum256(append(d, message[:]...)) 231 | i := new(big.Int).SetBytes(h[:]) 232 | k0 := i.Mod(i, Curve.N) 233 | if k0.Sign() == 0 { 234 | return nil, errors.New("k0 is zero") 235 | } 236 | 237 | return k0, nil 238 | } 239 | 240 | func deterministicGetRandA() (*big.Int, error) { 241 | a, err := rand.Int(rand.Reader, N2) 242 | if err != nil { 243 | return nil, err 244 | } 245 | 246 | return a.Add(a, One), nil 247 | } 248 | 249 | func intToByte(i *big.Int) []byte { 250 | b1, b2 := [32]byte{}, i.Bytes() 251 | copy(b1[32-len(b2):], b2) 252 | return b1[:] 253 | } 254 | 255 | // Marshal converts a point into the form specified in section 2.3.3 of the 256 | // SEC 1 standard. 257 | func Marshal(curve elliptic.Curve, x, y *big.Int) []byte { 258 | byteLen := (curve.Params().BitSize + 7) >> 3 259 | 260 | ret := make([]byte, 1+byteLen) 261 | ret[0] = 2 // compressed point 262 | 263 | xBytes := x.Bytes() 264 | copy(ret[1+byteLen-len(xBytes):], xBytes) 265 | ret[0] += byte(y.Bit(0)) 266 | return ret 267 | } 268 | 269 | // Unmarshal converts a point, serialised by Marshal, into an x, y pair. On 270 | // error, x = nil. 271 | func Unmarshal(curve elliptic.Curve, data []byte) (x, y *big.Int) { 272 | byteLen := (curve.Params().BitSize + 7) >> 3 273 | if (data[0] &^ 1) != 2 { 274 | return 275 | } 276 | if len(data) != 1+byteLen { 277 | return 278 | } 279 | 280 | x0 := new(big.Int).SetBytes(data[1 : 1+byteLen]) 281 | P := curve.Params().P 282 | ySq := new(big.Int) 283 | ySq.Exp(x0, Three, P) 284 | ySq.Add(ySq, Seven) 285 | ySq.Mod(ySq, P) 286 | y0 := new(big.Int) 287 | P1 := new(big.Int).Add(P, One) 288 | d := new(big.Int).Mod(P1, Four) 289 | P1.Sub(P1, d) 290 | P1.Div(P1, Four) 291 | y0.Exp(ySq, P1, P) 292 | 293 | if new(big.Int).Exp(y0, Two, P).Cmp(ySq) != 0 { 294 | return 295 | } 296 | if y0.Bit(0) != uint(data[0]&1) { 297 | y0.Sub(P, y0) 298 | } 299 | x, y = x0, y0 300 | return 301 | } 302 | -------------------------------------------------------------------------------- /schnorr_test.go: -------------------------------------------------------------------------------- 1 | package schnorr 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | "strings" 7 | "testing" 8 | 9 | "encoding/hex" 10 | ) 11 | 12 | func TestSign(t *testing.T) { 13 | for _, test := range testCases { 14 | if test.d == "" { 15 | continue 16 | } 17 | 18 | // given 19 | d := decodePrivateKey(test.d, t) 20 | m := decodeMessage(test.m, t) 21 | 22 | // when 23 | result, err := Sign(d, m) 24 | if err != nil { 25 | t.Fatalf("Unexpected error from Sign(%s, %s): %v", test.d, test.m, err) 26 | } 27 | 28 | observed := hex.EncodeToString(result[:]) 29 | expected := strings.ToLower(test.sig) 30 | 31 | // then 32 | if observed != expected { 33 | t.Fatalf("Sign(%s, %s) = %s, want %s", test.d, test.m, observed, expected) 34 | } 35 | } 36 | } 37 | 38 | func TestAggregateSignatures(t *testing.T) { 39 | pks := []*big.Int{} 40 | var ( 41 | m [32]byte 42 | pk [33]byte 43 | ) 44 | 45 | Pxs, Pys := []*big.Int{}, []*big.Int{} 46 | for i, test := range testCases { 47 | if test.d == "" { 48 | continue 49 | } 50 | 51 | privKey := decodePrivateKey(test.d, t) 52 | pks = append(pks, privKey) 53 | 54 | if i == 0 { 55 | m = decodeMessage(test.m, t) 56 | } 57 | 58 | Px, Py := Curve.ScalarBaseMult(privKey.Bytes()) 59 | Pxs = append(Pxs, Px) 60 | Pys = append(Pys, Py) 61 | } 62 | 63 | t.Run("Can sign and verify two aggregated signatures over same message", func(t *testing.T) { 64 | sig, err := AggregateSignatures(pks[:2], m) 65 | if err != nil { 66 | t.Fatalf("Unexpected error from AggregateSignatures(%x, %x): %v", pks[:2], m, err) 67 | } 68 | 69 | Px, Py := Curve.Add(Pxs[0], Pys[0], Pxs[1], Pys[1]) 70 | copy(pk[:], Marshal(Curve, Px, Py)) 71 | 72 | observedSum := hex.EncodeToString(pk[:]) 73 | expected := "02bca9ea6e07a63bec3d28a00329ac3d25d2595a5f86e512142affde48a34d9a97" 74 | 75 | // then 76 | if observedSum != expected { 77 | t.Fatalf("Sum of public keys, %s, want %s", observedSum, expected) 78 | } 79 | 80 | observed, err := Verify(pk, m, sig) 81 | if err != nil { 82 | t.Fatalf("Unexpected error from Verify(%x, %x, %x): %v", pk, m, sig, err) 83 | } 84 | 85 | // then 86 | if !observed { 87 | t.Fatalf("Verify(%x, %x, %x) = %v, want %v", pk, m, sig, observed, true) 88 | } 89 | }) 90 | 91 | t.Run("Can sign and verify two more aggregated signatures over same message", func(t *testing.T) { 92 | sig, err := AggregateSignatures(pks[1:3], m) 93 | if err != nil { 94 | t.Fatalf("Unexpected error from AggregateSignatures(%x, %x): %v", pks[1:3], m, err) 95 | } 96 | 97 | Px, Py := Curve.Add(Pxs[1], Pys[1], Pxs[2], Pys[2]) 98 | copy(pk[:], Marshal(Curve, Px, Py)) 99 | 100 | observedSum := hex.EncodeToString(pk[:]) 101 | expected := "03f0a6305d39a34582ba49a78bdf38ced935b3efce1e889d6820103665f35ee45b" 102 | 103 | // then 104 | if observedSum != expected { 105 | t.Fatalf("Sum of public keys, %s, want %s", observedSum, expected) 106 | } 107 | 108 | observed, err := Verify(pk, m, sig) 109 | if err != nil { 110 | t.Fatalf("Unexpected error from Verify(%x, %x, %x): %v", pk, m, sig, err) 111 | } 112 | 113 | // then 114 | if !observed { 115 | t.Fatalf("Verify(%x, %x, %x) = %v, want %v", pk, m, sig, observed, true) 116 | } 117 | }) 118 | 119 | t.Run("Can sign and verify three aggregated signatures over same message", func(t *testing.T) { 120 | sig, err := AggregateSignatures(pks[:3], m) 121 | if err != nil { 122 | t.Fatalf("Unexpected error from AggregateSignatures(%x, %x): %v", pks[:3], m, err) 123 | } 124 | 125 | Px, Py := Curve.Add(Pxs[0], Pys[0], Pxs[1], Pys[1]) 126 | Px, Py = Curve.Add(Px, Py, Pxs[2], Pys[2]) 127 | copy(pk[:], Marshal(Curve, Px, Py)) 128 | 129 | observedSum := hex.EncodeToString(pk[:]) 130 | expected := "025038e8f113785d84e86584d8a323debb1c212bf19c678185ddb1bb3e478ecde3" 131 | 132 | // then 133 | if observedSum != expected { 134 | t.Fatalf("Sum of public keys, %s, want %s", observedSum, expected) 135 | } 136 | 137 | observed, err := Verify(pk, m, sig) 138 | if err != nil { 139 | t.Fatalf("Unexpected error from Verify(%x, %x, %x): %v", pk, m, sig, err) 140 | } 141 | 142 | // then 143 | if !observed { 144 | t.Fatalf("Verify(%x, %x, %x) = %v, want %v", pk, m, sig, observed, true) 145 | } 146 | }) 147 | 148 | t.Run("Can aggregate and verify example in README", func(t *testing.T) { 149 | privKey1 := decodePrivateKey("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", t) 150 | privKey2 := decodePrivateKey("C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7", t) 151 | m := decodeMessage("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", t) 152 | 153 | pks := []*big.Int{privKey1, privKey2} 154 | aggregatedSignature, err := AggregateSignatures(pks, m) 155 | expected := "d60d7f81c15d57b04f8f6074de17f1b9eef2e0a9c9b2e93550c15b45d6998dc24ef5e393b356e7c334f36cee15e0f5f1e9ce06e7911793ddb9bd922d545b7525" 156 | observed := hex.EncodeToString(aggregatedSignature[:]) 157 | 158 | // then 159 | if observed != expected { 160 | t.Fatalf("AggregateSignatures(%x, %x) = %s, want %s", pks, m, observed, expected) 161 | } 162 | if err != nil { 163 | t.Fatalf("Unexpected error from AggregateSignatures(%x, %x): %v", pks, m, err) 164 | } 165 | 166 | // verifying an aggregated signature 167 | pubKey1 := decodePublicKey("02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", t) 168 | pubKey2 := decodePublicKey("03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", t) 169 | 170 | P1x, P1y := Unmarshal(Curve, pubKey1[:]) 171 | P2x, P2y := Unmarshal(Curve, pubKey2[:]) 172 | Px, Py := Curve.Add(P1x, P1y, P2x, P2y) 173 | 174 | copy(pk[:], Marshal(Curve, Px, Py)) 175 | 176 | observed = hex.EncodeToString(pk[:]) 177 | expected = "03f0a6305d39a34582ba49a78bdf38ced935b3efce1e889d6820103665f35ee45b" 178 | 179 | // then 180 | if observed != expected { 181 | t.Fatalf("Sum of public keys, %s, want %s", observed, expected) 182 | } 183 | 184 | result, err := Verify(pk, m, aggregatedSignature) 185 | if err != nil { 186 | t.Fatalf("Unexpected error from Verify(%x, %x, %x): %v", pk, m, aggregatedSignature, err) 187 | } 188 | 189 | // then 190 | if !result { 191 | t.Fatalf("Verify(%x, %x, %x) = %v, want %v", pk, m, aggregatedSignature, observed, true) 192 | } 193 | }) 194 | } 195 | 196 | func TestVerify(t *testing.T) { 197 | for _, test := range testCases { 198 | // given 199 | pk := decodePublicKey(test.pk, t) 200 | m := decodeMessage(test.m, t) 201 | sig := decodeSignature(test.sig, t) 202 | 203 | // when 204 | observed, err := Verify(pk, m, sig) 205 | if err != nil && (test.err == nil || err.Error() != test.err.Error()) { 206 | t.Fatalf("Unexpected error from Verify(%s, %s, %s): %v", test.pk, test.m, test.sig, err) 207 | } 208 | 209 | // then 210 | if observed != test.result { 211 | t.Fatalf("Verify(%s, %s, %s) = %v, want %v", test.pk, test.m, test.sig, observed, test.result) 212 | } 213 | } 214 | } 215 | 216 | type Batch struct { 217 | PublicKeys [][33]byte 218 | Messages [][32]byte 219 | Signatures [][64]byte 220 | } 221 | 222 | func (b *Batch) Append(pk [33]byte, m [32]byte, sig [64]byte) { 223 | b.PublicKeys = append(b.PublicKeys, pk) 224 | b.Messages = append(b.Messages, m) 225 | b.Signatures = append(b.Signatures, sig) 226 | } 227 | 228 | func (b *Batch) Merge(a *Batch) { 229 | b.PublicKeys = append(b.PublicKeys, a.PublicKeys...) 230 | b.Messages = append(b.Messages, a.Messages...) 231 | b.Signatures = append(b.Signatures, a.Signatures...) 232 | } 233 | 234 | func TestBatchVerify(t *testing.T) { 235 | valid := &Batch{} 236 | invalid := &Batch{} 237 | 238 | checkBatchVerify := func(b *Batch, expected bool, e error) { 239 | // when 240 | observed, err := BatchVerify(b.PublicKeys, b.Messages, b.Signatures) 241 | if err != nil && (e == nil || err.Error() != e.Error()) { 242 | t.Fatalf("Unexpected error from BatchVerify(%x, %x, %x): %v", b.PublicKeys, b.Messages, b.Signatures, err) 243 | } 244 | 245 | // then 246 | if expected != observed { 247 | t.Fatalf("BatchVerify(%x, %x, %x) = %v, want %v", b.PublicKeys, b.Messages, b.Signatures, observed, expected) 248 | } 249 | } 250 | 251 | for _, test := range testCases { 252 | // given 253 | pk := decodePublicKey(test.pk, t) 254 | m := decodeMessage(test.m, t) 255 | sig := decodeSignature(test.sig, t) 256 | 257 | if test.result { 258 | valid.Append(pk, m, sig) 259 | 260 | checkBatchVerify(valid, test.result, test.err) 261 | } else { 262 | invalid.Append(pk, m, sig) 263 | 264 | checkBatchVerify(invalid, test.result, errors.New("signature verification failed")) 265 | } 266 | 267 | checkBatchVerify(&Batch{[][33]byte{pk}, [][32]byte{m}, [][64]byte{sig}}, test.result, test.err) 268 | } 269 | 270 | // TODO add tests for nil and empty array parameters 271 | // BatchVerify(nil, nil, nil) 272 | checkBatchVerify(invalid, false, errors.New("signature verification failed")) 273 | } 274 | 275 | func BenchmarkSign(b *testing.B) { 276 | for i := 0; i < b.N; i++ { 277 | for _, test := range testCases { 278 | if test.d == "" { 279 | continue 280 | } 281 | 282 | d := decodePrivateKey(test.d, nil) 283 | m := decodeMessage(test.m, nil) 284 | Sign(d, m) 285 | } 286 | } 287 | } 288 | 289 | func BenchmarkVerify(b *testing.B) { 290 | for i := 0; i < b.N; i++ { 291 | for _, test := range testCases { 292 | pk := decodePublicKey(test.pk, nil) 293 | m := decodeMessage(test.m, nil) 294 | sig := decodeSignature(test.sig, nil) 295 | 296 | Verify(pk, m, sig) 297 | } 298 | } 299 | } 300 | 301 | func BenchmarkBatchVerify(b *testing.B) { 302 | for i := 0; i < b.N; i++ { 303 | for _, test := range testCases { 304 | pk := decodePublicKey(test.pk, nil) 305 | m := decodeMessage(test.m, nil) 306 | sig := decodeSignature(test.sig, nil) 307 | 308 | BatchVerify([][33]byte{pk}, [][32]byte{m}, [][64]byte{sig}) 309 | } 310 | } 311 | } 312 | 313 | func BenchmarkAggregateSignatures(b *testing.B) { 314 | for i := 0; i < b.N; i++ { 315 | privKey1 := decodePrivateKey("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", nil) 316 | privKey2 := decodePrivateKey("C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7", nil) 317 | m := decodeMessage("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", nil) 318 | 319 | pks := []*big.Int{privKey1, privKey2} 320 | AggregateSignatures(pks, m) 321 | } 322 | } 323 | 324 | func decodeSignature(s string, t *testing.T) (sig [64]byte) { 325 | signature, err := hex.DecodeString(s) 326 | if err != nil && t != nil { 327 | t.Fatalf("Unexpected error from hex.DecodeString(%s): %v", s, err) 328 | } 329 | copy(sig[:], signature) 330 | return 331 | } 332 | 333 | func decodeMessage(m string, t *testing.T) (msg [32]byte) { 334 | message, err := hex.DecodeString(m) 335 | if err != nil && t != nil { 336 | t.Fatalf("Unexpected error from hex.DecodeString(%s): %v", m, err) 337 | } 338 | copy(msg[:], message) 339 | return 340 | } 341 | 342 | func decodePublicKey(pk string, t *testing.T) (pubKey [33]byte) { 343 | publicKey, err := hex.DecodeString(pk) 344 | if err != nil && t != nil { 345 | t.Fatalf("Unexpected error from hex.DecodeString(%s): %v", pk, err) 346 | } 347 | copy(pubKey[:], publicKey) 348 | return 349 | } 350 | 351 | func decodePrivateKey(d string, t *testing.T) *big.Int { 352 | privKey, ok := new(big.Int).SetString(d, 16) 353 | if !ok && t != nil { 354 | t.Fatalf("Unexpected error from new(big.Int).SetString(%s, 16)", d) 355 | } 356 | return privKey 357 | } 358 | --------------------------------------------------------------------------------