mirror of
https://github.com/status-im/keycard-go.git
synced 2025-02-28 11:30:29 +00:00
add derivationpath pkg
This commit is contained in:
parent
19e1a9ade1
commit
7c52fa75c3
214
derivationpath/derivationpath.go
Normal file
214
derivationpath/derivationpath.go
Normal file
@ -0,0 +1,214 @@
|
||||
package keycard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StartingPoint int
|
||||
|
||||
const (
|
||||
tokenMaster = 0x6D // char m
|
||||
tokenSeparator = 0x2F // char /
|
||||
tokenHardened = 0x27 // char '
|
||||
tokenDot = 0x2E // char .
|
||||
|
||||
hardenedStart = 0x80000000 // 2^31
|
||||
)
|
||||
|
||||
const (
|
||||
StartingPointMaster StartingPoint = iota + 1
|
||||
StartingPointCurrent
|
||||
StartingPointParent
|
||||
)
|
||||
|
||||
type parseFunc = func() error
|
||||
|
||||
type parser struct {
|
||||
r *strings.Reader
|
||||
f parseFunc
|
||||
pos int
|
||||
path []uint32
|
||||
start StartingPoint
|
||||
currentToken string
|
||||
currentTokenHardened bool
|
||||
}
|
||||
|
||||
func newParser(path string) *parser {
|
||||
p := &parser{
|
||||
r: strings.NewReader(path),
|
||||
}
|
||||
|
||||
p.reset()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *parser) reset() {
|
||||
p.r.Seek(0, io.SeekStart)
|
||||
p.pos = 0
|
||||
p.start = StartingPointCurrent
|
||||
p.f = p.parseStart
|
||||
p.path = make([]uint32, 0)
|
||||
p.resetCurrentToken()
|
||||
}
|
||||
|
||||
func (p *parser) resetCurrentToken() {
|
||||
p.currentToken = ""
|
||||
p.currentTokenHardened = false
|
||||
}
|
||||
|
||||
func (p *parser) parse() (StartingPoint, []uint32, error) {
|
||||
for {
|
||||
err := p.f()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf("at position %d, %s", p.pos, err.Error())
|
||||
}
|
||||
|
||||
return p.start, p.path, err
|
||||
}
|
||||
}
|
||||
|
||||
return p.start, p.path, nil
|
||||
}
|
||||
|
||||
func (p *parser) readByte() (byte, error) {
|
||||
b, err := p.r.ReadByte()
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
p.pos++
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *parser) unreadByte() error {
|
||||
err := p.r.UnreadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.pos--
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseStart() error {
|
||||
b, err := p.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b == tokenMaster {
|
||||
p.start = StartingPointMaster
|
||||
p.f = p.parseSeparator
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == tokenDot {
|
||||
b2, err := p.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b2 == tokenDot {
|
||||
p.f = p.parseSeparator
|
||||
p.start = StartingPointParent
|
||||
return nil
|
||||
}
|
||||
|
||||
p.f = p.parseSeparator
|
||||
p.start = StartingPointCurrent
|
||||
return p.unreadByte()
|
||||
}
|
||||
|
||||
p.f = p.parseSegment
|
||||
|
||||
return p.unreadByte()
|
||||
}
|
||||
|
||||
func (p *parser) saveSegment() error {
|
||||
if len(p.currentToken) > 0 {
|
||||
i, err := strconv.ParseUint(p.currentToken, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i >= hardenedStart {
|
||||
p.pos -= len(p.currentToken) - 1
|
||||
return fmt.Errorf("index must be lower than 2^31, got %d", i)
|
||||
}
|
||||
|
||||
if p.currentTokenHardened {
|
||||
i += hardenedStart
|
||||
}
|
||||
|
||||
p.path = append(p.path, uint32(i))
|
||||
}
|
||||
|
||||
p.f = p.parseSegment
|
||||
p.resetCurrentToken()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseSeparator() error {
|
||||
b, err := p.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b == tokenSeparator {
|
||||
return p.saveSegment()
|
||||
}
|
||||
|
||||
return fmt.Errorf("expected %s, got %s", string(tokenSeparator), string(b))
|
||||
}
|
||||
|
||||
func (p *parser) parseSegment() error {
|
||||
b, err := p.readByte()
|
||||
if err == io.EOF {
|
||||
if len(p.currentToken) == 0 {
|
||||
return fmt.Errorf("expected number, got EOF")
|
||||
}
|
||||
|
||||
if newErr := p.saveSegment(); newErr != nil {
|
||||
return newErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(p.currentToken) > 0 && b == tokenSeparator {
|
||||
return p.saveSegment()
|
||||
}
|
||||
|
||||
if len(p.currentToken) > 0 && b == tokenHardened {
|
||||
p.currentTokenHardened = true
|
||||
p.f = p.parseSeparator
|
||||
return nil
|
||||
}
|
||||
|
||||
if b < 0x30 || b > 0x39 {
|
||||
return fmt.Errorf("expected number, got %s", string(b))
|
||||
}
|
||||
|
||||
p.currentToken = fmt.Sprintf("%s%s", p.currentToken, string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Parse(str string) (StartingPoint, []uint32, error) {
|
||||
p := newParser(str)
|
||||
return p.parse()
|
||||
}
|
86
derivationpath/derivationpath_test.go
Normal file
86
derivationpath/derivationpath_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package keycard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
path string
|
||||
expectedPath []uint32
|
||||
expectedStartingPoint StartingPoint
|
||||
err error
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
expectedPath: []uint32{},
|
||||
expectedStartingPoint: StartingPointCurrent,
|
||||
},
|
||||
{
|
||||
path: "1",
|
||||
expectedPath: []uint32{1},
|
||||
expectedStartingPoint: StartingPointCurrent,
|
||||
},
|
||||
{
|
||||
path: "..",
|
||||
expectedPath: []uint32{},
|
||||
expectedStartingPoint: StartingPointParent,
|
||||
},
|
||||
{
|
||||
path: "m",
|
||||
expectedPath: []uint32{},
|
||||
expectedStartingPoint: StartingPointMaster,
|
||||
},
|
||||
{
|
||||
path: "m/1",
|
||||
expectedPath: []uint32{1},
|
||||
expectedStartingPoint: StartingPointMaster,
|
||||
},
|
||||
{
|
||||
path: "m/1/2",
|
||||
expectedPath: []uint32{1, 2},
|
||||
expectedStartingPoint: StartingPointMaster,
|
||||
},
|
||||
{
|
||||
path: "m/1/2'/3",
|
||||
expectedPath: []uint32{1, 2147483650, 3},
|
||||
expectedStartingPoint: StartingPointMaster,
|
||||
},
|
||||
{
|
||||
path: "m/",
|
||||
err: fmt.Errorf("at position 2, expected number, got EOF"),
|
||||
},
|
||||
{
|
||||
path: "m/1//2",
|
||||
err: fmt.Errorf("at position 5, expected number, got /"),
|
||||
},
|
||||
{
|
||||
path: "m/1'2",
|
||||
err: fmt.Errorf("at position 5, expected /, got 2"),
|
||||
},
|
||||
{
|
||||
path: "m/'/2",
|
||||
err: fmt.Errorf("at position 3, expected number, got '"),
|
||||
},
|
||||
{
|
||||
path: "m/2147483648",
|
||||
err: fmt.Errorf("at position 3, index must be lower than 2^31, got 2147483648"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
t.Run(fmt.Sprintf("scenario %d", i), func(t *testing.T) {
|
||||
startingPoint, path, err := Parse(s.path)
|
||||
if s.err == nil {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedStartingPoint, startingPoint)
|
||||
assert.Equal(t, s.expectedPath, path)
|
||||
} else {
|
||||
assert.Equal(t, s.err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user