mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-02 09:45:50 +00:00
f3f6662fc9
* Initial draft of the neo-go wallet * Cleanup + more test for util package * integrated wallet into neo-cli partially * base wallet implementation + smartcontract code.
193 lines
4.2 KiB
Go
193 lines
4.2 KiB
Go
package wallet
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
|
"github.com/anthdm/rfc6979"
|
|
"golang.org/x/crypto/ripemd160"
|
|
)
|
|
|
|
// PrivateKey represents a NEO private key.
|
|
type PrivateKey struct {
|
|
b []byte
|
|
}
|
|
|
|
func NewPrivateKey() (*PrivateKey, error) {
|
|
c := crypto.NewEllipticCurve()
|
|
b := make([]byte, c.N.BitLen()/8+8)
|
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d := new(big.Int).SetBytes(b)
|
|
d.Mod(d, new(big.Int).Sub(c.N, big.NewInt(1)))
|
|
d.Add(d, big.NewInt(1))
|
|
|
|
p := &PrivateKey{b: d.Bytes()}
|
|
return p, nil
|
|
}
|
|
|
|
// NewPrivateKeyFromHex returns a PrivateKey created from the
|
|
// given hex string.
|
|
func NewPrivateKeyFromHex(str string) (*PrivateKey, error) {
|
|
b, err := hex.DecodeString(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewPrivateKeyFromBytes(b)
|
|
}
|
|
|
|
// NewPrivateKeyFromBytes returns a NEO PrivateKey from the given byte slice.
|
|
func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
|
if len(b) != 32 {
|
|
return nil, fmt.Errorf(
|
|
"invalid byte length: expected %d bytes got %d", 32, len(b),
|
|
)
|
|
}
|
|
return &PrivateKey{b}, nil
|
|
}
|
|
|
|
// PublicKey derives the public key from the private key.
|
|
func (p *PrivateKey) PublicKey() ([]byte, error) {
|
|
var (
|
|
c = crypto.NewEllipticCurve()
|
|
q = new(big.Int).SetBytes(p.b)
|
|
)
|
|
|
|
point := c.ScalarBaseMult(q)
|
|
if !c.IsOnCurve(point) {
|
|
return nil, errors.New("failed to derive public key using elliptic curve")
|
|
}
|
|
|
|
bx := point.X.Bytes()
|
|
padded := append(
|
|
bytes.Repeat(
|
|
[]byte{0x00},
|
|
32-len(bx),
|
|
),
|
|
bx...,
|
|
)
|
|
|
|
prefix := []byte{0x03}
|
|
if point.Y.Bit(0) == 0 {
|
|
prefix = []byte{0x02}
|
|
}
|
|
b := append(prefix, padded...)
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// NewPrivateKeyFromWIF returns a NEO PrivateKey from the given
|
|
// WIF (wallet import format).
|
|
func NewPrivateKeyFromWIF(wif string) (*PrivateKey, error) {
|
|
w, err := WIFDecode(wif, WIFVersion)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.PrivateKey, nil
|
|
}
|
|
|
|
// WIF returns the (wallet import format) of the PrivateKey.
|
|
// Good documentation about this process can be found here:
|
|
// https://en.bitcoin.it/wiki/Wallet_import_format
|
|
func (p *PrivateKey) WIF() (string, error) {
|
|
return WIFEncode(p.b, WIFVersion, true)
|
|
}
|
|
|
|
// Address derives the public NEO address that is coupled with the private key, and
|
|
// returns it as a string.
|
|
func (p *PrivateKey) Address() (string, error) {
|
|
b, err := p.Signature()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
b = append([]byte{0x17}, b...)
|
|
|
|
sha := sha256.New()
|
|
sha.Write(b)
|
|
hash := sha.Sum(nil)
|
|
|
|
sha.Reset()
|
|
sha.Write(hash)
|
|
hash = sha.Sum(nil)
|
|
|
|
b = append(b, hash[0:4]...)
|
|
|
|
address := crypto.Base58Encode(b)
|
|
|
|
return address, nil
|
|
}
|
|
|
|
// Signature creates the signature using the private key.
|
|
func (p *PrivateKey) Signature() ([]byte, error) {
|
|
b, err := p.PublicKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b = append([]byte{0x21}, b...)
|
|
b = append(b, 0xAC)
|
|
|
|
sha := sha256.New()
|
|
sha.Write(b)
|
|
hash := sha.Sum(nil)
|
|
|
|
ripemd := ripemd160.New()
|
|
ripemd.Reset()
|
|
ripemd.Write(hash)
|
|
hash = ripemd.Sum(nil)
|
|
|
|
return hash, nil
|
|
}
|
|
|
|
// Sign signs arbitrary length data using the private key.
|
|
func (p *PrivateKey) Sign(data []byte) ([]byte, error) {
|
|
var (
|
|
privateKey = p.ecdsa()
|
|
digest = sha256.Sum256(data)
|
|
)
|
|
|
|
r, s, err := rfc6979.SignECDSA(privateKey, digest[:], sha256.New)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := privateKey.Curve.Params()
|
|
curveOrderByteSize := params.P.BitLen() / 8
|
|
rBytes, sBytes := r.Bytes(), s.Bytes()
|
|
signature := make([]byte, curveOrderByteSize*2)
|
|
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
|
|
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
|
|
|
|
return signature, nil
|
|
}
|
|
|
|
// ecsda converts the key to a usable ecsda.PrivateKey for signing data.
|
|
func (p *PrivateKey) ecdsa() *ecdsa.PrivateKey {
|
|
priv := new(ecdsa.PrivateKey)
|
|
priv.PublicKey.Curve = elliptic.P256()
|
|
priv.D = new(big.Int).SetBytes(p.b)
|
|
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(p.b)
|
|
return priv
|
|
}
|
|
|
|
// String implements the stringer interface.
|
|
func (p *PrivateKey) String() string {
|
|
return hex.EncodeToString(p.b)
|
|
}
|
|
|
|
// Bytes returns the underlying bytes of the PrivateKey.
|
|
func (p *PrivateKey) Bytes() []byte {
|
|
return p.b
|
|
}
|