This commit is contained in:
Luxferre
2026-09-19 18:26:07 +03:00
parent 8ae332e74d
commit e9826b1ae2
28 changed files with 3417 additions and 1 deletions
+292
View File
@@ -0,0 +1,292 @@
// Package secp256k1 implements the secp256k1 elliptic curve arithmetic and
// recoverable ECDSA signing used by TRON. The Go standard library does not
// provide this curve, so it is built directly on math/big following SEC 1.
package secp256k1
import (
"crypto/hmac"
"crypto/sha256"
"errors"
"math/big"
)
// Curve parameters for secp256k1.
var (
// P is the field prime.
P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
// N is the group order.
N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
// Gx, Gy are the coordinates of the base point.
Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
halfN = new(big.Int).Rsh(N, 1)
)
// Errors returned by the package.
var (
ErrInvalidKey = errors.New("secp256k1: invalid private key")
ErrInvalidSig = errors.New("secp256k1: invalid signature")
ErrNoRecover = errors.New("secp256k1: public key recovery failed")
)
// Point is an affine point on the curve. A nil point is the point at infinity.
type Point struct {
X, Y *big.Int
}
func mod(x, m *big.Int) *big.Int {
r := new(big.Int).Mod(x, m)
if r.Sign() < 0 {
r.Add(r, m)
}
return r
}
// Add returns p + q on the curve.
func Add(p, q *Point) *Point {
if p == nil {
return q
}
if q == nil {
return p
}
if p.X.Cmp(q.X) == 0 {
if p.Y.Cmp(q.Y) != 0 || p.Y.Sign() == 0 {
return nil
}
return double(p)
}
num := mod(new(big.Int).Sub(q.Y, p.Y), P)
den := mod(new(big.Int).Sub(q.X, p.X), P)
den.ModInverse(den, P)
lam := mod(new(big.Int).Mul(num, den), P)
return applyLambda(p, q, lam)
}
func double(p *Point) *Point {
num := new(big.Int).Mul(p.X, p.X)
num.Mul(num, big.NewInt(3))
num = mod(num, P)
den := new(big.Int).Lsh(p.Y, 1)
den = mod(den, P)
den.ModInverse(den, P)
lam := mod(new(big.Int).Mul(num, den), P)
return applyLambda(p, p, lam)
}
func applyLambda(p, q *Point, lam *big.Int) *Point {
x3 := new(big.Int).Mul(lam, lam)
x3.Sub(x3, p.X)
x3.Sub(x3, q.X)
x3 = mod(x3, P)
y3 := new(big.Int).Sub(p.X, x3)
y3.Mul(lam, y3)
y3.Sub(y3, p.Y)
y3 = mod(y3, P)
return &Point{X: x3, Y: y3}
}
// ScalarBaseMult returns k*G.
func ScalarBaseMult(k *big.Int) *Point {
return ScalarMult(k, &Point{X: Gx, Y: Gy})
}
// ScalarMult returns k*p using a double-and-add ladder.
func ScalarMult(k *big.Int, p *Point) *Point {
var r *Point
if p == nil || k.Sign() == 0 {
return nil
}
kk := mod(k, N)
for i := kk.BitLen() - 1; i >= 0; i-- {
r = Add(r, r)
if kk.Bit(i) == 1 {
r = Add(r, p)
}
}
return r
}
// IsOnCurve reports whether p satisfies the curve equation.
func IsOnCurve(p *Point) bool {
if p == nil || p.X == nil || p.Y == nil {
return false
}
if p.X.Sign() < 0 || p.X.Cmp(P) >= 0 || p.Y.Sign() < 0 || p.Y.Cmp(P) >= 0 {
return false
}
left := mod(new(big.Int).Mul(p.Y, p.Y), P)
right := new(big.Int).Mul(p.X, p.X)
right.Mul(right, p.X)
right.Add(right, big.NewInt(7))
right = mod(right, P)
return left.Cmp(right) == 0
}
// PublicKey derives the public key point from a 32-byte private key.
func PublicKey(priv []byte) (*Point, error) {
if len(priv) != 32 {
return nil, ErrInvalidKey
}
k := new(big.Int).SetBytes(priv)
if k.Sign() == 0 || k.Cmp(N) >= 0 {
return nil, ErrInvalidKey
}
return ScalarBaseMult(k), nil
}
// Signature is a recoverable ECDSA signature (r, s) with recovery id V.
type Signature struct {
R, S *big.Int
V byte
}
// Bytes returns the 65-byte compact representation r || s || v.
func (sig *Signature) Bytes() []byte {
out := make([]byte, 65)
sig.R.FillBytes(out[0:32])
sig.S.FillBytes(out[32:64])
out[64] = sig.V
return out
}
// Sign signs a 32-byte message hash with the given private key, producing a
// deterministic recoverable signature (libsecp256k1-compatible nonce, low-s
// form). This matches coincurve/libsecp256k1's default recoverable signing.
func Sign(priv, hash []byte) (*Signature, error) {
if len(priv) != 32 {
return nil, ErrInvalidKey
}
if len(hash) != 32 {
return nil, ErrInvalidSig
}
d := new(big.Int).SetBytes(priv)
if d.Sign() == 0 || d.Cmp(N) >= 0 {
return nil, ErrInvalidKey
}
e := mod(new(big.Int).SetBytes(hash), N)
for {
k := nonceRFC6979(priv, hash)
kp := ScalarBaseMult(k)
if kp == nil {
continue
}
r := mod(kp.X, N)
if r.Sign() == 0 {
continue
}
kinv := new(big.Int).ModInverse(k, N)
s := new(big.Int).Mul(r, d)
s.Add(s, e)
s.Mul(s, kinv)
s.Mod(s, N)
if s.Sign() == 0 {
continue
}
v := byte(kp.Y.Bit(0))
if s.Cmp(halfN) > 0 {
s.Sub(N, s)
v ^= 1
}
return &Signature{R: r, S: s, V: v}, nil
}
}
// nonceRFC6979 reproduces libsecp256k1's default RFC 6979 nonce function. It
// feeds HMAC-SHA256 with the private key, the message and 48 zero bytes of
// optional data, exactly as secp256k1_nonce_function_rfc6979 does when no
// extra entropy is supplied.
func nonceRFC6979(priv, hash []byte) *big.Int {
// libsecp256k1's rfc6979 default nonce feeds exactly key32||msg32 (no
// extra data, no algo16), i.e. a 64-byte HMAC key.
keydata := make([]byte, 64)
copy(keydata[0:32], priv)
copy(keydata[32:64], hash)
v := make([]byte, 32)
for i := range v {
v[i] = 0x01
}
k := make([]byte, 32)
k = hmacSHA256(k, v, []byte{0x00}, keydata[:])
v = hmacSHA256(k, v)
k = hmacSHA256(k, v, []byte{0x01}, keydata[:])
v = hmacSHA256(k, v)
for {
v = hmacSHA256(k, v)
candidate := new(big.Int).SetBytes(v)
if candidate.Sign() > 0 && candidate.Cmp(N) < 0 {
return candidate
}
k = hmacSHA256(k, v, []byte{0x00})
v = hmacSHA256(k, v)
}
}
func hmacSHA256(key []byte, parts ...[]byte) []byte {
mac := hmac.New(sha256.New, key)
for _, p := range parts {
mac.Write(p)
}
return mac.Sum(nil)
}
// RecoverPublicKey recovers the public key point from a message hash and a
// recoverable signature.
func RecoverPublicKey(hash []byte, sig *Signature) (*Point, error) {
if sig == nil || sig.R == nil || sig.S == nil || len(hash) != 32 {
return nil, ErrInvalidSig
}
if sig.R.Sign() == 0 || sig.S.Sign() == 0 || sig.R.Cmp(N) >= 0 || sig.S.Cmp(N) >= 0 {
return nil, ErrInvalidSig
}
x := new(big.Int).Set(sig.R)
if sig.V&2 != 0 {
x.Add(x, N)
}
if x.Cmp(P) >= 0 {
return nil, ErrNoRecover
}
alpha := new(big.Int).Mul(x, x)
alpha.Mul(alpha, x)
alpha.Add(alpha, big.NewInt(7))
alpha = mod(alpha, P)
beta := sqrtMod(alpha)
if beta == nil {
return nil, ErrNoRecover
}
var y *big.Int
if byte(beta.Bit(0)) == sig.V&1 {
y = beta
} else {
y = new(big.Int).Sub(P, beta)
}
rp := &Point{X: x, Y: y}
e := mod(new(big.Int).SetBytes(hash), N)
rinv := new(big.Int).ModInverse(sig.R, N)
u1 := mod(new(big.Int).Neg(e), N)
u1.Mul(u1, rinv)
u1.Mod(u1, N)
u2 := mod(new(big.Int).Mul(sig.S, rinv), N)
q := Add(ScalarBaseMult(u1), ScalarMult(u2, rp))
if q == nil || !IsOnCurve(q) {
return nil, ErrNoRecover
}
return q, nil
}
// sqrtMod returns a square root of a modulo P (P = 3 mod 4), or nil.
func sqrtMod(a *big.Int) *big.Int {
exp := new(big.Int).Add(P, big.NewInt(1))
exp.Rsh(exp, 2)
r := new(big.Int).Exp(a, exp, P)
if mod(new(big.Int).Mul(r, r), P).Cmp(mod(a, P)) != 0 {
return nil
}
return r
}
+130
View File
@@ -0,0 +1,130 @@
package secp256k1
import (
"encoding/hex"
"math/big"
"testing"
)
func mustDecodeHex(t *testing.T, s string) []byte {
t.Helper()
b, err := hex.DecodeString(s)
if err != nil {
t.Fatalf("decode hex: %v", err)
}
return b
}
// TestBasePointOnCurve confirms the generator satisfies y^2 = x^3 + 7.
func TestBasePointOnCurve(t *testing.T) {
if !IsOnCurve(&Point{X: Gx, Y: Gy}) {
t.Fatalf("generator is not on the curve")
}
}
// TestPointDoublingMatchesScalarMult verifies group consistency: 2*G computed
// via point doubling equals k*G for k=2.
func TestPointDoublingMatchesScalarMult(t *testing.T) {
g := &Point{X: Gx, Y: Gy}
viaDouble := Add(g, g)
viaScalar := ScalarBaseMult(big.NewInt(2))
if viaDouble.X.Cmp(viaScalar.X) != 0 || viaDouble.Y.Cmp(viaScalar.Y) != 0 {
t.Fatalf("2G mismatch: double=%v scalar=%v", viaDouble, viaScalar)
}
if !IsOnCurve(viaScalar) {
t.Fatalf("2G not on curve")
}
}
// TestScalarMultDistributes checks (a+b)G == aG + bG.
func TestScalarMultDistributes(t *testing.T) {
a := big.NewInt(12345)
b := big.NewInt(67890)
sum := new(big.Int).Add(a, b)
left := ScalarBaseMult(sum)
right := Add(ScalarBaseMult(a), ScalarBaseMult(b))
if left.X.Cmp(right.X) != 0 || left.Y.Cmp(right.Y) != 0 {
t.Fatalf("(a+b)G != aG + bG")
}
}
// TestPublicKeyKnownValues locks the uncompressed public key for the t3 key.
func TestPublicKeyKnownValues(t *testing.T) {
priv := mustDecodeHex(t, "a96135266ba58bb5b3a450b8fe7a9d59ea3e6cb0cdb2300e8138d2d2d48e61a8")
point, err := PublicKey(priv)
if err != nil {
t.Fatalf("PublicKey: %v", err)
}
raw := make([]byte, 64)
point.X.FillBytes(raw[0:32])
point.Y.FillBytes(raw[32:64])
want := "076e3db946fcb53d575c4e742a9f8ff8c4b57ded53776b2a80d13022e1647adaa1641f3e2210f5f6a3bdd73d073ac029ba79d576093d8a92dd148e448a8e63cb"
if got := hex.EncodeToString(raw); got != want {
t.Fatalf("pubkey = %s, want %s", got, want)
}
if !IsOnCurve(point) {
t.Fatalf("derived public key not on curve")
}
}
// TestSignRecoverRoundTrip signs and recovers for several keys and hashes.
func TestSignRecoverRoundTrip(t *testing.T) {
privs := []string{
"a96135266ba58bb5b3a450b8fe7a9d59ea3e6cb0cdb2300e8138d2d2d48e61a8",
"3252a4cb9b3865c94dbb7d6f50107ca704c1c2b9e3d1eb81753e221c7db8fa25",
"993638df18a38ef2be9f8c915eb9063647d015edc8fb70e3bcb32469bee39c21",
}
hashes := []string{
"e93945efde601eed484553338e58218f4e81c51261dfda72db2d874f75378fe8",
"0000000000000000000000000000000000000000000000000000000000000001",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
}
for _, p := range privs {
priv := mustDecodeHex(t, p)
wantPoint, _ := PublicKey(priv)
for _, h := range hashes {
hash := mustDecodeHex(t, h)
sig, err := Sign(priv, hash)
if err != nil {
t.Fatalf("Sign: %v", err)
}
// low-s enforcement
if sig.S.Cmp(halfN) > 0 {
t.Fatalf("signature s is not low-s")
}
recovered, err := RecoverPublicKey(hash, sig)
if err != nil {
t.Fatalf("RecoverPublicKey: %v", err)
}
if recovered.X.Cmp(wantPoint.X) != 0 || recovered.Y.Cmp(wantPoint.Y) != 0 {
t.Fatalf("recovered key differs for priv=%s hash=%s", p, h)
}
}
}
}
// TestSignIsDeterministic confirms RFC6979-style reproducibility.
func TestSignIsDeterministic(t *testing.T) {
priv := mustDecodeHex(t, "a96135266ba58bb5b3a450b8fe7a9d59ea3e6cb0cdb2300e8138d2d2d48e61a8")
hash := mustDecodeHex(t, "e93945efde601eed484553338e58218f4e81c51261dfda72db2d874f75378fe8")
a, err := Sign(priv, hash)
if err != nil {
t.Fatalf("Sign: %v", err)
}
b, err := Sign(priv, hash)
if err != nil {
t.Fatalf("Sign: %v", err)
}
if hex.EncodeToString(a.Bytes()) != hex.EncodeToString(b.Bytes()) {
t.Fatalf("signatures differ for identical inputs")
}
}
func TestPublicKeyRejectsInvalid(t *testing.T) {
if _, err := PublicKey(make([]byte, 31)); err == nil {
t.Fatalf("expected error for short key")
}
if _, err := PublicKey(make([]byte, 32)); err == nil {
t.Fatalf("expected error for zero key")
}
}