mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-22 10:16:18 +08:00
eip-5564 stealth address POC
This commit is contained in:
parent
8264b5f42c
commit
3953f279fc
48
ecdh/ecdh.go
48
ecdh/ecdh.go
@ -33,6 +33,10 @@ type Curve interface {
|
||||
// private keys can cause ECDH to return an error.
|
||||
NewPrivateKey(key []byte) (*PrivateKey, error)
|
||||
|
||||
// GenerateKeyFromScalar generates a PrivateKey from a scalar.
|
||||
// This is used for testing only now.
|
||||
GenerateKeyFromScalar(scalar []byte) (*PrivateKey, error)
|
||||
|
||||
// NewPublicKey checks that key is valid and returns a PublicKey.
|
||||
//
|
||||
// For NIST curves, this decodes an uncompressed point according to SEC 1,
|
||||
@ -50,6 +54,20 @@ type Curve interface {
|
||||
// methods in the future without breaking backwards compatibility.
|
||||
ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error)
|
||||
|
||||
// addPublicKeys adds two public keys and returns the resulting public key. It's exposed
|
||||
// as the PublicKey.Add method.
|
||||
addPublicKeys(a, b *PublicKey) (*PublicKey, error)
|
||||
|
||||
// addPrivateKeys adds two private keys and returns the resulting private key. It's exposed
|
||||
// as the PrivateKey.Add method.
|
||||
addPrivateKeys(a, b *PrivateKey) (*PrivateKey, error)
|
||||
|
||||
// secretKey generates a shared secret key from a local ephemeral private key and a
|
||||
// remote public key. It's exposed as the PrivateKey.SecretKey method.
|
||||
//
|
||||
// This method is similar to ECDH, but it returns the raw shared secret instead
|
||||
secretKey(local *PrivateKey, remote *PublicKey) ([]byte, error)
|
||||
|
||||
// sm2mqv performs a SM2 specific style ECMQV exchange and return the shared secret.
|
||||
sm2mqv(sLocal, eLocal *PrivateKey, sRemote, eRemote *PublicKey) (*PublicKey, error)
|
||||
|
||||
@ -136,6 +154,15 @@ func (uv *PublicKey) SM2SharedKey(isResponder bool, kenLen int, sPub, sRemote *P
|
||||
return sm3.Kdf(buffer[:], kenLen), nil
|
||||
}
|
||||
|
||||
// Add adds two public keys and returns the resulting public key.
|
||||
// k is NOT changed.
|
||||
func (k *PublicKey) Add(x *PublicKey) (*PublicKey, error) {
|
||||
if k.curve != x.curve {
|
||||
return nil, errors.New("ecdh: public keys do not have the same curve")
|
||||
}
|
||||
return k.curve.addPublicKeys(k, x)
|
||||
}
|
||||
|
||||
// PrivateKey is an ECDH private key, usually kept secret.
|
||||
//
|
||||
// These keys can be parsed with [smx509.ParsePKCS8PrivateKey] and encoded
|
||||
@ -165,6 +192,18 @@ func (k *PrivateKey) ECDH(remote *PublicKey) ([]byte, error) {
|
||||
return k.curve.ecdh(k, remote)
|
||||
}
|
||||
|
||||
// SecretKey generates a shared secret key from a local ephemeral private key and a
|
||||
// remote public key.
|
||||
//
|
||||
// This method is similar to [ECDH], but it returns the raw shared secret instead
|
||||
// of the x-coordinate of the shared point.
|
||||
func (k *PrivateKey) SecretKey(remote *PublicKey) ([]byte, error) {
|
||||
if k.curve != remote.curve {
|
||||
return nil, errors.New("ecdh: private key and public key curves do not match")
|
||||
}
|
||||
return k.curve.secretKey(k, remote)
|
||||
}
|
||||
|
||||
// SM2MQV performs a SM2 specific style ECMQV exchange and return the shared secret.
|
||||
func (k *PrivateKey) SM2MQV(eLocal *PrivateKey, sRemote, eRemote *PublicKey) (*PublicKey, error) {
|
||||
if k.curve != eLocal.curve || k.curve != sRemote.curve || k.curve != eRemote.curve {
|
||||
@ -213,3 +252,12 @@ func (k *PrivateKey) PublicKey() *PublicKey {
|
||||
func (k *PrivateKey) Public() crypto.PublicKey {
|
||||
return k.PublicKey()
|
||||
}
|
||||
|
||||
// Add adds two private keys and returns the resulting private key.
|
||||
// k is NOT changed.
|
||||
func (k *PrivateKey) Add(x *PrivateKey) (*PrivateKey, error) {
|
||||
if k.curve != x.curve {
|
||||
return nil, errors.New("ecdh: private keys do not have the same curve")
|
||||
}
|
||||
return k.curve.addPrivateKeys(k, x)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"math/bits"
|
||||
|
||||
"github.com/emmansun/gmsm/internal/bigmod"
|
||||
"github.com/emmansun/gmsm/internal/randutil"
|
||||
sm2ec "github.com/emmansun/gmsm/internal/sm2ec"
|
||||
"github.com/emmansun/gmsm/internal/subtle"
|
||||
@ -83,6 +84,21 @@ func (c *sm2Curve) privateKeyToPublicKey(key *PrivateKey) *PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sm2Curve) GenerateKeyFromScalar(scalar []byte) (*PrivateKey, error) {
|
||||
if size := len(c.scalarOrderMinus1); len(scalar) > size {
|
||||
scalar = scalar[:size]
|
||||
}
|
||||
m, err := bigmod.NewModulus(c.scalarOrderMinus1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := bigmod.NewNat().SetOverflowingBytes(scalar, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.NewPrivateKey(p.Bytes(m))
|
||||
}
|
||||
|
||||
func (c *sm2Curve) NewPublicKey(key []byte) (*PublicKey, error) {
|
||||
// Reject the point at infinity and compressed encodings.
|
||||
if len(key) == 0 || key[0] != 4 {
|
||||
@ -111,6 +127,47 @@ func (c *sm2Curve) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) {
|
||||
return p.BytesX()
|
||||
}
|
||||
|
||||
func (c *sm2Curve) addPublicKeys(a, b *PublicKey) (*PublicKey, error) {
|
||||
p1, err := c.newPoint().SetBytes(a.publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p2, err := c.newPoint().SetBytes(b.publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p1.Add(p1, p2)
|
||||
return c.NewPublicKey(p1.Bytes())
|
||||
}
|
||||
|
||||
func (c *sm2Curve) addPrivateKeys(a, b *PrivateKey) (*PrivateKey, error) {
|
||||
m, err := bigmod.NewModulus(c.scalarOrderMinus1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aNat, err := bigmod.NewNat().SetBytes(a.privateKey, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bNat, err := bigmod.NewNat().SetBytes(b.privateKey, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aNat = aNat.Add(bNat, m)
|
||||
return c.NewPrivateKey(aNat.Bytes(m))
|
||||
}
|
||||
|
||||
func (c *sm2Curve) secretKey(local *PrivateKey, remote *PublicKey) ([]byte, error) {
|
||||
p, err := c.newPoint().SetBytes(remote.publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := p.ScalarMult(p, local.privateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *sm2Curve) sm2avf(secret *PublicKey) []byte {
|
||||
bytes := secret.publicKey[1:33]
|
||||
var result [32]byte
|
||||
|
122
ecdh/stealth_test.go
Normal file
122
ecdh/stealth_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package ecdh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/emmansun/gmsm/sm3"
|
||||
)
|
||||
|
||||
// https://eips.ethereum.org/EIPS/eip-5564, but uses SM3 instead of Keccak256
|
||||
|
||||
// Generation - Generate stealth address from stealth meta-address
|
||||
func generateStealthAddress(spendPub, viewPub *PublicKey) (ephemeralPub *PublicKey, stealth *PublicKey, err error) {
|
||||
// generate ephemeral key pair
|
||||
ephemeralPriv, err := P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ephemeralPub = ephemeralPriv.PublicKey()
|
||||
|
||||
// compute shared secret key
|
||||
R, err := ephemeralPriv.SecretKey(viewPub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// the secret key is hashed
|
||||
sh := sm3.Sum(R[1:])
|
||||
|
||||
// multiply the hashed shared secret with the generator point
|
||||
shPriv, err := P256().GenerateKeyFromScalar(sh[:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
shPublic := shPriv.PublicKey()
|
||||
|
||||
// compute the recipient's stealth public key
|
||||
stealth, err = shPublic.Add(spendPub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return ephemeralPub, stealth, nil
|
||||
}
|
||||
|
||||
// Parsing - Locate one’s own stealth address
|
||||
func checkStealthAddress(viewPriv *PrivateKey, spendPub, ephemeralPub, stealth *PublicKey) (bool, error) {
|
||||
// compute shared secret key
|
||||
R, err := viewPriv.SecretKey(ephemeralPub)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// the secret key is hashed
|
||||
sh := sm3.Sum(R[1:])
|
||||
// multiply the hashed shared secret with the generator point
|
||||
shPriv, err := P256().GenerateKeyFromScalar(sh[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
shPublic := shPriv.PublicKey()
|
||||
// compute the derived stealth address
|
||||
goStealth, err := shPublic.Add(spendPub)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// compare the derived stealth address with the provided stealth address
|
||||
return stealth.Equal(goStealth), nil
|
||||
}
|
||||
|
||||
// Private key derivation - Generate the stealth address private key from the hashed shared secret and the spending private key.
|
||||
func computeStealthKey(spendPriv, viewPriv *PrivateKey, ephemeralPub *PublicKey) (*PrivateKey, error) {
|
||||
// compute shared secret key
|
||||
R, err := viewPriv.SecretKey(ephemeralPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// the secret key is hashed
|
||||
sh := sm3.Sum(R[1:])
|
||||
// multiply the hashed shared secret with the generator point
|
||||
shPriv, err := P256().GenerateKeyFromScalar(sh[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spendPriv.Add(shPriv)
|
||||
}
|
||||
|
||||
func testEIP5564StealthAddress(t *testing.T, spendPriv, viewPriv *PrivateKey) {
|
||||
t.Helper()
|
||||
|
||||
ephemeralPub, expectedStealth, err := generateStealthAddress(spendPriv.PublicKey(), viewPriv.PublicKey())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("the recipient's stealth public key: failed to add public keys: %v", err)
|
||||
}
|
||||
|
||||
passed, err := checkStealthAddress(viewPriv, spendPriv.PublicKey(), ephemeralPub, expectedStealth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !passed {
|
||||
t.Fatal("mismatched stealth address")
|
||||
}
|
||||
|
||||
privStealth, err := computeStealthKey(spendPriv, viewPriv, ephemeralPub)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compute stealth key: %v", err)
|
||||
}
|
||||
if !privStealth.PublicKey().Equal(expectedStealth) {
|
||||
t.Fatal("mismatched stealth key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP5564StealthAddress(t *testing.T) {
|
||||
privSpend, err := P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate private key: %v", err)
|
||||
}
|
||||
privView, err := P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate private key: %v", err)
|
||||
}
|
||||
testEIP5564StealthAddress(t, privSpend, privView)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user