forked from stellar/go
/
main.go
143 lines (123 loc) · 3.19 KB
/
main.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
package derivation
import (
"bytes"
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"errors"
"regexp"
"strconv"
"strings"
"github.com/agl/ed25519"
)
const (
// StellarAccountPrefix is a prefix for Stellar key pairs derivation.
StellarAccountPrefix = "m/44'/148'"
// StellarPrimaryAccountPath is a derivation path of the primary account.
StellarPrimaryAccountPath = "m/44'/148'/0'"
// StellarAccountPathFormat is a path format used for Stellar key pair
// derivation as described in SEP-00XX. Use with `fmt.Sprintf` and `DeriveForPath`.
StellarAccountPathFormat = "m/44'/148'/%d'"
// FirstHardenedIndex is the index of the first hardened key.
FirstHardenedIndex = uint32(0x80000000)
// As in https://github.com/satoshilabs/slips/blob/master/slip-0010.md
seedModifier = "ed25519 seed"
)
var (
ErrInvalidPath = errors.New("Invalid derivation path")
ErrNoPublicDerivation = errors.New("No public derivation for ed25519")
pathRegex = regexp.MustCompile("^m(\\/[0-9]+')+$")
)
type Key struct {
Key []byte
ChainCode []byte
}
// DeriveForPath derives key for a path in BIP-44 format and a seed.
// Ed25119 derivation operated on hardened keys only.
func DeriveForPath(path string, seed []byte) (*Key, error) {
if !isValidPath(path) {
return nil, ErrInvalidPath
}
key, err := NewMasterKey(seed)
if err != nil {
return nil, err
}
segments := strings.Split(path, "/")
for _, segment := range segments[1:] {
i64, err := strconv.ParseUint(strings.TrimRight(segment, "'"), 10, 32)
if err != nil {
return nil, err
}
// We operate on hardened keys
i := uint32(i64) + FirstHardenedIndex
key, err = key.Derive(i)
if err != nil {
return nil, err
}
}
return key, nil
}
// NewMasterKey generates a new master key from seed.
func NewMasterKey(seed []byte) (*Key, error) {
hmac := hmac.New(sha512.New, []byte(seedModifier))
_, err := hmac.Write(seed)
if err != nil {
return nil, err
}
sum := hmac.Sum(nil)
key := &Key{
Key: sum[:32],
ChainCode: sum[32:],
}
return key, nil
}
func (k *Key) Derive(i uint32) (*Key, error) {
// no public derivation for ed25519
if i < FirstHardenedIndex {
return nil, ErrNoPublicDerivation
}
iBytes := make([]byte, 4)
binary.BigEndian.PutUint32(iBytes, i)
key := append([]byte{0x0}, k.Key...)
data := append(key, iBytes...)
hmac := hmac.New(sha512.New, k.ChainCode)
_, err := hmac.Write(data)
if err != nil {
return nil, err
}
sum := hmac.Sum(nil)
newKey := &Key{
Key: sum[:32],
ChainCode: sum[32:],
}
return newKey, nil
}
// PublicKey returns public key for a derived private key.
func (k *Key) PublicKey() ([]byte, error) {
reader := bytes.NewReader(k.Key)
pub, _, err := ed25519.GenerateKey(reader)
if err != nil {
return nil, err
}
return pub[:], nil
}
// RawSeed returns raw seed bytes
func (k *Key) RawSeed() [32]byte {
var rawSeed [32]byte
copy(rawSeed[:], k.Key[:])
return rawSeed
}
func isValidPath(path string) bool {
if !pathRegex.MatchString(path) {
return false
}
// Check for overflows
segments := strings.Split(path, "/")
for _, segment := range segments[1:] {
_, err := strconv.ParseUint(strings.TrimRight(segment, "'"), 10, 32)
if err != nil {
return false
}
}
return true
}