-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
ethschnorr.go
152 lines (137 loc) · 5.15 KB
/
ethschnorr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Package ethschnorr implements a version of the Schnorr signature which is
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// cheap to verify on-chain.
//
// See https://en.wikipedia.org/wiki/Schnorr_signature For vanilla Schnorr.
//
// Since we are targeting ethereum specifically, there is no need to abstract
// away the group operations, as original kyber Schnorr code does. Thus, these
// functions only work with secp256k1 objects, even though they are expressed in
// terms of the abstract kyber Group interfaces.
//
// This code is largely based on EPFL-DEDIS's go.dedis.ch/kyber/sign/schnorr
package ethschnorr
import (
"bytes"
"fmt"
"math/big"
"github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1"
"go.dedis.ch/kyber/v3"
)
var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1()
var secp256k1Group kyber.Group = secp256k1Suite
type signature = struct {
CommitmentPublicAddress [20]byte
Signature *big.Int
}
// Signature is a representation of the Schnorr signature generated and verified
// by this library.
type Signature = *signature
func i() *big.Int { return big.NewInt(0) }
var one = big.NewInt(1)
var u256Cardinality = i().Lsh(one, 256)
var maxUint256 = i().Sub(u256Cardinality, one)
// NewSignature allocates space for a Signature, and returns it
func NewSignature() Signature { return &signature{Signature: i()} }
var zero = i()
// ValidSignature(s) is true iff s.Signature represents an element of secp256k1
func ValidSignature(s Signature) bool {
return s.Signature.Cmp(secp256k1.GroupOrder) == -1 &&
s.Signature.Cmp(zero) != -1
}
// ChallengeHash returns the value the signer must use to demonstrate knowledge
// of the secret key
//
// NB: for parity with the on-chain hash, it's important that public and r
// marshall to the big-endian x ordinate, followed by a byte which is 0 if the y
// ordinate is even, 1 if it's odd. See evm/contracts/SchnorrSECP256K1.sol and
// evm/test/schnorr_test.js
func ChallengeHash(public kyber.Point, rAddress [20]byte, msg *big.Int) (
kyber.Scalar, error) {
var err error
h := secp256k1Suite.Hash()
if _, herr := public.MarshalTo(h); herr != nil {
err = fmt.Errorf("failed to hash public key for signature: %s", herr)
}
if err != nil && (msg.BitLen() > 256 || msg.Cmp(zero) == -1) {
err = fmt.Errorf("msg must be a uint256")
}
if err == nil {
if _, herr := h.Write(msg.Bytes()); herr != nil {
err = fmt.Errorf("failed to hash message for signature: %s", herr)
}
}
if err == nil {
if _, herr := h.Write(rAddress[:]); herr != nil {
err = fmt.Errorf("failed to hash r for signature: %s", herr)
}
}
if err != nil {
return nil, err
}
return secp256k1Suite.Scalar().SetBytes(h.Sum(nil)), nil
}
// Sign creates a signature from a msg and a private key. Verify with the
// function Verify, or on-chain with SchnorrSECP256K1.sol.
func Sign(private kyber.Scalar, msg *big.Int) (Signature, error) {
if !secp256k1.IsSecp256k1Scalar(private) {
return nil, fmt.Errorf("private key is not a secp256k1 scalar")
}
// create random secret and public commitment to it
commitmentSecretKey := secp256k1Group.Scalar().Pick(
secp256k1Suite.RandomStream())
commitmentPublicKey := secp256k1Group.Point().Mul(commitmentSecretKey, nil)
commitmentPublicAddress := secp256k1.EthereumAddress(commitmentPublicKey)
public := secp256k1Group.Point().Mul(private, nil)
challenge, err := ChallengeHash(public, commitmentPublicAddress, msg)
if err != nil {
return nil, err
}
// commitmentSecretKey-private*challenge
s := secp256k1Group.Scalar().Sub(commitmentSecretKey,
secp256k1Group.Scalar().Mul(private, challenge))
rv := signature{commitmentPublicAddress, secp256k1.ToInt(s)}
return &rv, nil
}
// Verify verifies the given Schnorr signature. It returns true iff the
// signature is valid.
func Verify(public kyber.Point, msg *big.Int, s Signature) error {
var err error
if !ValidSignature(s) {
err = fmt.Errorf("s is not a valid signature")
}
if err == nil && !secp256k1.IsSecp256k1Point(public) {
err = fmt.Errorf("public key is not a secp256k1 point")
}
if err == nil && !secp256k1.ValidPublicKey(public) {
err = fmt.Errorf("`public` is not a valid public key")
}
if err == nil && (msg.Cmp(zero) == -1 || msg.Cmp(maxUint256) == 1) {
err = fmt.Errorf("msg is not a uint256")
}
var challenge kyber.Scalar
var herr error
if err == nil {
challenge, herr = ChallengeHash(public, s.CommitmentPublicAddress, msg)
if herr != nil {
err = herr
}
}
if err != nil {
return err
}
sigScalar := secp256k1.IntToScalar(s.Signature)
// s*g + challenge*public = s*g + challenge*(secretKey*g) =
// commitmentSecretKey*g = commitmentPublicKey
maybeCommitmentPublicKey := secp256k1Group.Point().Add(
secp256k1Group.Point().Mul(sigScalar, nil),
secp256k1Group.Point().Mul(challenge, public))
maybeCommitmentPublicAddress := secp256k1.EthereumAddress(maybeCommitmentPublicKey)
if !bytes.Equal(s.CommitmentPublicAddress[:],
maybeCommitmentPublicAddress[:]) {
return fmt.Errorf("signature mismatch")
}
return nil
}