Skip to content

Commit fe7f4a9

Browse files
committed
secp256k1: add DLEQVerify
A quick port of https://github.com/BlockstreamResearch/secp256k1-zkp/blob/6152622613fdf1c5af6f31f74c427c4e9ee120ce/src/modules/ecdsa_adaptor/dleq_impl.h#L129 A DLEQ (discrete log equivalence) proof proves that the discrete log of P1 to the secp256k1 base G is the same as the discrete log of P2 to another base. This is useful to verify that a silent payment (BIP-352) output created by a signer was created correctly.
1 parent 014e2b2 commit fe7f4a9

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

api/firmware/secp256k1.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package firmware
1616

1717
import (
18+
"bytes"
1819
"crypto/sha256"
1920
"math/big"
2021

@@ -57,3 +58,60 @@ func antikleptoVerify(hostNonce, signerCommitment, signature []byte) error {
5758
}
5859
return nil
5960
}
61+
62+
// Verifies a DLEQ proof.
63+
//
64+
// A DLEQ (discrete log equivalence) proof proves that the discrete log of p1 to the secp256k1 base
65+
// G is the same as the discrete log of p2 to another base gen2.
66+
//
67+
// Same as
68+
// https://github.com/BlockstreamResearch/secp256k1-zkp/blob/6152622613fdf1c5af6f31f74c427c4e9ee120ce/src/modules/ecdsa_adaptor/dleq_impl.h#L129
69+
// with default noncefp and ndata==NULL).
70+
func DLEQVerify(proof []byte, p1, gen2, p2 *btcec.PublicKey) error {
71+
if len(proof) != 64 {
72+
return errp.New("proof must be 64 bytes")
73+
}
74+
s := proof[:32]
75+
e := proof[32:]
76+
curve := btcec.S256()
77+
78+
// R1 = s*G - e*P1
79+
sPubX, sPubY := curve.ScalarBaseMult(s)
80+
eP1X, eP1Y := curve.ScalarMult(p1.X(), p1.Y(), e)
81+
// Negate eP1
82+
eP1Y = new(big.Int).Sub(curve.P, eP1Y)
83+
r1PubX, r1PubY := curve.Add(sPubX, sPubY, eP1X, eP1Y)
84+
85+
/* R2 = s*gen2 - e*P2 */
86+
sGen2X, sGen2Y := curve.ScalarMult(gen2.X(), gen2.Y(), s)
87+
eP2X, eP2Y := curve.ScalarMult(p2.X(), p2.Y(), e)
88+
// Negate eP2
89+
eP2Y = new(big.Int).Sub(curve.P, eP2Y)
90+
r2PubX, r2PubY := curve.Add(sGen2X, sGen2Y, eP2X, eP2Y)
91+
92+
toPub := func(x, y *big.Int) *btcec.PublicKey {
93+
var xx, yy btcec.FieldVal
94+
xx.SetByteSlice(x.Bytes())
95+
yy.SetByteSlice(y.Bytes())
96+
return btcec.NewPublicKey(&xx, &yy)
97+
}
98+
challenge := func() *big.Int {
99+
var b bytes.Buffer
100+
b.Write(p1.SerializeCompressed())
101+
b.Write(gen2.SerializeCompressed())
102+
b.Write(p2.SerializeCompressed())
103+
b.Write(toPub(r1PubX, r1PubY).SerializeCompressed())
104+
b.Write(toPub(r2PubX, r2PubY).SerializeCompressed())
105+
hash := taggedSha256([]byte("DLEQ"), b.Bytes())
106+
return new(big.Int).SetBytes(hash)
107+
}
108+
modEqual := func(x, y, n *big.Int) bool {
109+
return new(big.Int).Mod(x, n).Cmp(new(big.Int).Mod(y, n)) == 0
110+
}
111+
eExpected := challenge()
112+
eInt := new(big.Int).SetBytes(e)
113+
if !modEqual(eExpected, eInt, curve.N) {
114+
return errp.New("DLEQ proof verification failed")
115+
}
116+
return nil
117+
}

api/firmware/secp256k1_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package firmware
33
import (
44
"testing"
55

6+
"github.com/btcsuite/btcd/btcec/v2"
67
"github.com/stretchr/testify/require"
78
)
89

@@ -48,3 +49,22 @@ func TestAntikleptoVerify(t *testing.T) {
4849
require.Error(t, antikleptoVerify(test.hostNonce, test.signerCommitment, test.signature))
4950
}
5051
}
52+
53+
func TestDLEQVerify(t *testing.T) {
54+
// secret key sk=077eb75a52eca24cdedf058c92f1ca8b9d4841771fd6baa3d27885fb5b49fba2
55+
// is the secret key in pubKey=sk*G and otherPubKey=sk*otherBase.
56+
//
57+
// Test fixture created by running dleq_verify() in
58+
// https://github.com/BlockstreamResearch/secp256k1-zkp/blob/6152622613fdf1c5af6f31f74c427c4e9ee120ce/src/modules/ecdsa_adaptor/dleq_impl.h
59+
// with the above secret key, p1 being sk*G,
60+
// 0389140f7bb852f020f154e55908fe3699dc9f65153e681527f0d55aabed937f4b for gen2, and p2=sk*gen2.
61+
62+
proof := unhex("6c885f825f6ce7565bc6d0bfda90506b11e2682dfe943f5a85badf1c8a96edc5f5e03f5ee2c58bf979646fbada920f9f1c5bd92805fb5b01534b42d26a550f79")
63+
pubKey, err := btcec.ParsePubKey(unhex("021b8594084a12da837a78f9337e3d29169025a8ddbe9f9933a131607c6e62d21e"))
64+
require.NoError(t, err)
65+
otherBase, err := btcec.ParsePubKey(unhex("0389140f7bb852f020f154e55908fe3699dc9f65153e681527f0d55aabed937f4b"))
66+
require.NoError(t, err)
67+
otherPubKey, err := btcec.ParsePubKey(unhex("03019807775b92c83d24e3001b55f60373e0bdc7d7cc7bc86befecf5bb987b54ac"))
68+
require.NoError(t, err)
69+
require.NoError(t, DLEQVerify(proof, pubKey, otherBase, otherPubKey))
70+
}

0 commit comments

Comments
 (0)