mirror of
https://github.com/mineracks/seedhammer-v1-companion.git
synced 2026-06-26 22:01:05 +10:00
Twelve packages lifted from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d8f23eb7fd39b15fc0acb8874113f801e):
address/ — bitcoin address parsing
backup/ — v1 plate dimensions + UR-coded multi-plate backup
bc/ — Blockchain Commons: ur, fountain, bytewords, urtypes,
xoshiro256 (5 subpkgs)
bip32/ — HD-key derivation
bip39/ — mnemonic seed phrases + 2048-word wordlist
engrave/ — text/QR → MoveTo/LineTo command stream conversion
seedqr/ — SeedQR / CompactSeedQR encoders
image/ — paletted, rgb565, alpha4, ninepatch image formats
nonstandard/ — bitcoin descriptor + script parsing
font/ — bitmap + vector font runtime
font/{comfortaa,poppins,constant,bitmap,vector}/ — actual fonts
driver/mjolnir/ — MarkingWay USB-serial engraver driver
Plus an earlier-aside backup_test.go restored (its deps are now lifted).
Import paths globally rewritten seedhammer.com → mineracks namespace
via single sed pass; verified no orphan refs remain. go.mod adopts
upstream's full dep set plus the replace-directive for the patched
kortschak/qr fork.
go build ./... clean (all 27 packages)
go test ./... clean (12 packages with tests, all passing)
NOT lifted in this commit:
- driver/{wshat,drm,libcamera} (hardware-specific GPIO/LCD/camera —
will be platform-v1/-shaped abstractions instead)
- gui/ (depends on the above; lifts in Phase 2)
- cmd/{controller,...} (Pi binary entrypoints — not needed for the
companion repo)
- zbar/ (QR scanner — needs libcamera)
Next:
- Write the SH1E reference encoder/decoder in engrave/wire/sh1e/
- Lift Gangleri42's cmd/webnfc/ shell + retune to v1 plates
- First buildable composer WASM with a working preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
641 lines
15 KiB
Go
641 lines
15 KiB
Go
// Package urtypes implements decoders for UR types specified in [BCR-2020-006].
|
|
//
|
|
// [BCR-2020-006]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md
|
|
package urtypes
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/fxamacker/cbor/v2"
|
|
)
|
|
|
|
type OutputDescriptor struct {
|
|
Title string
|
|
Script Script
|
|
Threshold int
|
|
Type MultisigType
|
|
Keys []KeyDescriptor
|
|
}
|
|
|
|
type KeyDescriptor struct {
|
|
Network *chaincfg.Params
|
|
MasterFingerprint uint32
|
|
DerivationPath Path
|
|
Children []Derivation
|
|
KeyData []byte
|
|
ChainCode []byte
|
|
ParentFingerprint uint32
|
|
}
|
|
|
|
type Derivation struct {
|
|
Type DerivationType
|
|
// Index is the child index, without the hardening offset.
|
|
// For RangeDerivations, Index is the start of the range.
|
|
Index uint32
|
|
Hardened bool
|
|
// End represents the end of a RangeDerivation.
|
|
End uint32
|
|
}
|
|
|
|
type DerivationType int
|
|
|
|
const (
|
|
ChildDerivation DerivationType = iota
|
|
WildcardDerivation
|
|
RangeDerivation
|
|
)
|
|
|
|
type Script int
|
|
|
|
const (
|
|
UnknownScript Script = iota
|
|
P2SH
|
|
P2SH_P2WSH
|
|
P2SH_P2WPKH
|
|
P2PKH
|
|
P2WSH
|
|
P2WPKH
|
|
P2TR
|
|
)
|
|
|
|
func (s Script) String() string {
|
|
switch s {
|
|
case P2SH:
|
|
return "Legacy (P2SH)"
|
|
case P2SH_P2WSH:
|
|
return "Nested Segwit (P2SH-P2WSH)"
|
|
case P2SH_P2WPKH:
|
|
return "Nested Segwit (P2SH-P2WPKH)"
|
|
case P2PKH:
|
|
return "Legacy (P2PKH)"
|
|
case P2WSH:
|
|
return "Segwit (P2WSH)"
|
|
case P2WPKH:
|
|
return "Segwit (P2WPKH)"
|
|
case P2TR:
|
|
return "Taproot (P2TR)"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
type MultisigType int
|
|
|
|
const (
|
|
Singlesig MultisigType = iota
|
|
SortedMulti
|
|
)
|
|
|
|
// Singlesig reports whether the script is for single-sig.
|
|
func (s Script) Singlesig() bool {
|
|
for _, s2 := range []Script{P2PKH, P2WPKH, P2SH_P2WPKH, P2TR} {
|
|
if s == s2 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DerivationPath returns the standard derivation path
|
|
// for the script. It panics if the script is unknown.
|
|
func (s Script) DerivationPath() Path {
|
|
switch s {
|
|
case P2WPKH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 84,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
}
|
|
case P2PKH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 44,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
}
|
|
case P2SH_P2WPKH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 49,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
}
|
|
case P2TR:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 86,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
}
|
|
case P2SH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 45,
|
|
}
|
|
case P2SH_P2WSH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 48,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 1,
|
|
}
|
|
case P2WSH:
|
|
return Path{
|
|
hdkeychain.HardenedKeyStart + 48,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 0,
|
|
hdkeychain.HardenedKeyStart + 2,
|
|
}
|
|
}
|
|
panic("unknown script")
|
|
}
|
|
|
|
// Encode the output descriptor in the format described by
|
|
// [BCR-2020-010].
|
|
//
|
|
// [BCR-2020-010]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-010-output-desc.md
|
|
func (o OutputDescriptor) Encode() []byte {
|
|
var v any
|
|
switch o.Type {
|
|
case SortedMulti:
|
|
m := struct {
|
|
Threshold int `cbor:"1,keyasint,omitempty"`
|
|
Keys []cbor.Tag `cbor:"2,keyasint"`
|
|
}{
|
|
Threshold: o.Threshold,
|
|
}
|
|
for _, k := range o.Keys {
|
|
m.Keys = append(m.Keys, cbor.Tag{
|
|
Number: tagHDKey,
|
|
Content: k.toCBOR(),
|
|
})
|
|
}
|
|
v = cbor.Tag{
|
|
Number: uint64(tagSortedMulti),
|
|
Content: m,
|
|
}
|
|
case Singlesig:
|
|
v = cbor.Tag{
|
|
Number: tagHDKey,
|
|
Content: o.Keys[0].toCBOR(),
|
|
}
|
|
default:
|
|
panic("invalid type")
|
|
}
|
|
var tags []uint64
|
|
switch o.Script {
|
|
case P2SH:
|
|
tags = []uint64{tagSH}
|
|
case P2SH_P2WSH:
|
|
tags = []uint64{tagSH, tagWSH}
|
|
case P2SH_P2WPKH:
|
|
tags = []uint64{tagSH, tagWPKH}
|
|
case P2PKH:
|
|
tags = []uint64{tagP2PKH}
|
|
case P2WSH:
|
|
tags = []uint64{tagWSH}
|
|
case P2WPKH:
|
|
tags = []uint64{tagWPKH}
|
|
case P2TR:
|
|
tags = []uint64{tagTR}
|
|
default:
|
|
panic("invalid type")
|
|
}
|
|
for i := len(tags) - 1; i >= 0; i-- {
|
|
v = cbor.Tag{
|
|
Number: tags[i],
|
|
Content: v,
|
|
}
|
|
}
|
|
enc, err := encMode.Marshal(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return enc
|
|
}
|
|
|
|
func (k KeyDescriptor) ExtendedKey() *hdkeychain.ExtendedKey {
|
|
var fp [4]byte
|
|
binary.BigEndian.PutUint32(fp[:], k.ParentFingerprint)
|
|
childNum := uint32(0)
|
|
if len(k.DerivationPath) > 0 {
|
|
childNum = k.DerivationPath[len(k.DerivationPath)-1]
|
|
}
|
|
return hdkeychain.NewExtendedKey(
|
|
k.Network.HDPublicKeyID[:],
|
|
k.KeyData, k.ChainCode, fp[:], uint8(len(k.DerivationPath)),
|
|
childNum, false,
|
|
)
|
|
}
|
|
|
|
func (k KeyDescriptor) String() string {
|
|
return k.ExtendedKey().String()
|
|
}
|
|
|
|
// Encode the key in the format described by [BCR-2020-007].
|
|
//
|
|
// [BCR-2020-007]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md
|
|
func (k KeyDescriptor) toCBOR() hdKey {
|
|
var children []any
|
|
for _, c := range k.Children {
|
|
switch c.Type {
|
|
case ChildDerivation:
|
|
children = append(children, c.Index, c.Hardened)
|
|
case RangeDerivation:
|
|
children = append(children, c.Index, c.End, c.Hardened)
|
|
case WildcardDerivation:
|
|
children = append(children, []any{}, c.Hardened)
|
|
}
|
|
}
|
|
depth := len(k.DerivationPath)
|
|
if depth == len(k.DerivationPath) {
|
|
// No need to store the depth if the derivation path is present.
|
|
depth = 0
|
|
}
|
|
network := mainnet
|
|
if k.Network == &chaincfg.TestNet3Params {
|
|
network = testnet
|
|
}
|
|
return hdKey{
|
|
UseInfo: useInfo{
|
|
Network: network,
|
|
},
|
|
KeyData: k.KeyData,
|
|
ChainCode: k.ChainCode,
|
|
ParentFingerprint: k.ParentFingerprint,
|
|
Origin: keyPath{
|
|
Fingerprint: k.MasterFingerprint,
|
|
Depth: uint8(depth),
|
|
Components: k.DerivationPath.components(),
|
|
},
|
|
Children: keyPath{
|
|
Components: children,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Encode the key in the format described by [BCR-2020-007].
|
|
//
|
|
// [BCR-2020-007]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md
|
|
func (k KeyDescriptor) Encode() []byte {
|
|
b, err := encMode.Marshal(k.toCBOR())
|
|
if err != nil {
|
|
// Always valid by construction.
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
type Path []uint32
|
|
|
|
func (p Path) components() []any {
|
|
var comp []any
|
|
for _, c := range p {
|
|
hard := c >= hdkeychain.HardenedKeyStart
|
|
if hard {
|
|
c -= hdkeychain.HardenedKeyStart
|
|
}
|
|
comp = append(comp, c, hard)
|
|
}
|
|
return comp
|
|
}
|
|
|
|
func (p Path) String() string {
|
|
var d strings.Builder
|
|
d.WriteRune('m')
|
|
for _, p := range p {
|
|
d.WriteByte('/')
|
|
idx := p
|
|
if p >= hdkeychain.HardenedKeyStart {
|
|
idx -= hdkeychain.HardenedKeyStart
|
|
}
|
|
d.WriteString(strconv.Itoa(int(idx)))
|
|
if p >= hdkeychain.HardenedKeyStart {
|
|
d.WriteRune('h')
|
|
}
|
|
}
|
|
return d.String()
|
|
}
|
|
|
|
type seed struct {
|
|
Payload []byte `cbor:"1,keyasint"`
|
|
}
|
|
|
|
type multi struct {
|
|
Threshold int `cbor:"1,keyasint"`
|
|
Keys []cbor.RawMessage `cbor:"2,keyasint"`
|
|
}
|
|
|
|
// account is the CBOR representation of a crypto-account.
|
|
type account struct {
|
|
MasterFingerprint uint32 `cbor:"1,keyasint,omitempty"`
|
|
OutputDescriptors []cbor.RawMessage `cbor:"2,keyasint,omitempty"`
|
|
}
|
|
|
|
// hdKey is the CBOR representation of a crypto-hdkey.
|
|
type hdKey struct {
|
|
IsMaster bool `cbor:"1,keyasint,omitempty"`
|
|
IsPrivate bool `cbor:"2,keyasint,omitempty"`
|
|
KeyData []byte `cbor:"3,keyasint"`
|
|
ChainCode []byte `cbor:"4,keyasint,omitempty"`
|
|
UseInfo useInfo `cbor:"5,keyasint,omitempty"`
|
|
Origin keyPath `cbor:"6,keyasint,omitempty"`
|
|
Children keyPath `cbor:"7,keyasint,omitempty"`
|
|
ParentFingerprint uint32 `cbor:"8,keyasint,omitempty"`
|
|
}
|
|
|
|
type useInfo struct {
|
|
Type uint32 `cbor:"1,keyasint,omitempty"`
|
|
Network int `cbor:"2,keyasint,omitempty"`
|
|
}
|
|
|
|
type keyPath struct {
|
|
Components []any `cbor:"1,keyasint,omitempty"`
|
|
Fingerprint uint32 `cbor:"2,keyasint,omitempty"`
|
|
Depth uint8 `cbor:"3,keyasint,omitempty"`
|
|
}
|
|
|
|
const (
|
|
tagHDKey = 303
|
|
tagKeyPath = 304
|
|
tagUseInfo = 305
|
|
|
|
tagSH = 400
|
|
tagWSH = 401
|
|
tagP2PKH = 403
|
|
tagWPKH = 404
|
|
tagTR = 409
|
|
|
|
tagMulti = 406
|
|
tagSortedMulti = 407
|
|
)
|
|
|
|
var encMode cbor.EncMode
|
|
var decMode cbor.DecMode
|
|
|
|
func init() {
|
|
tags := cbor.NewTagSet()
|
|
if err := tags.Add(cbor.TagOptions{DecTag: cbor.DecTagOptional}, reflect.TypeOf(hdKey{}), tagHDKey); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := tags.Add(cbor.TagOptions{DecTag: cbor.DecTagOptional, EncTag: cbor.EncTagRequired}, reflect.TypeOf(keyPath{}), tagKeyPath); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := tags.Add(cbor.TagOptions{DecTag: cbor.DecTagOptional, EncTag: cbor.EncTagRequired}, reflect.TypeOf(useInfo{}), tagUseInfo); err != nil {
|
|
panic(err)
|
|
}
|
|
em, err := cbor.CoreDetEncOptions().EncModeWithTags(tags)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
encMode = em
|
|
dm, err := cbor.DecOptions{}.DecModeWithTags(tags)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
decMode = dm
|
|
}
|
|
|
|
func Parse(typ string, enc []byte) (any, error) {
|
|
switch typ {
|
|
case "crypto-seed":
|
|
var s seed
|
|
if err := decMode.Unmarshal(enc, &s); err != nil {
|
|
return nil, fmt.Errorf("ur: %s: %w", typ, err)
|
|
}
|
|
return s, nil
|
|
case "crypto-account":
|
|
// Limited support for crypto-account: unpack a single output
|
|
// descriptor and treat it like crypto-output.
|
|
var acc account
|
|
if err := decMode.Unmarshal(enc, &acc); err != nil {
|
|
return nil, fmt.Errorf("ur: crypto-account: %w", err)
|
|
}
|
|
if len(acc.OutputDescriptors) != 1 {
|
|
return nil, fmt.Errorf("ur: crypto-account: zero or multiple crypto-outputs")
|
|
}
|
|
enc = acc.OutputDescriptors[0]
|
|
desc, err := parseOutputDescriptor(decMode, acc.OutputDescriptors[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ur: crypto-account: %w", err)
|
|
}
|
|
if !desc.Script.Singlesig() {
|
|
return nil, fmt.Errorf("ur: crypto-account: invalid single-sig script: %s", desc.Script)
|
|
}
|
|
return desc, nil
|
|
case "crypto-output":
|
|
desc, err := parseOutputDescriptor(decMode, enc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ur: crypto-output: %w", err)
|
|
}
|
|
return desc, nil
|
|
case "crypto-hdkey":
|
|
key, err := parseHDKey(enc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ur: crypto-hdkey: %w", err)
|
|
}
|
|
return key, nil
|
|
case "bytes":
|
|
var content []byte
|
|
if err := decMode.Unmarshal(enc, &content); err != nil {
|
|
return nil, fmt.Errorf("ur: bytes decoding failed: %w", err)
|
|
}
|
|
return content, nil
|
|
default:
|
|
return nil, fmt.Errorf("ur: unknown type %q", typ)
|
|
}
|
|
}
|
|
|
|
const mainnet = 0
|
|
const testnet = 1
|
|
|
|
func parseHDKey(enc []byte) (KeyDescriptor, error) {
|
|
var k hdKey
|
|
if err := decMode.Unmarshal(enc, &k); err != nil {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: crypto-hdkey decoding failed: %w", err)
|
|
}
|
|
const cointypeBTC = 0
|
|
if k.UseInfo.Type != cointypeBTC {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: crypto-hdkey key has unsupported coin type %d", k.UseInfo.Type)
|
|
}
|
|
children, err := parseKeypath(k.Children.Components)
|
|
if err != nil {
|
|
return KeyDescriptor{}, err
|
|
}
|
|
if len(k.KeyData) != 33 {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: crypto-hdkey key is %d bytes, expected 33", len(k.KeyData))
|
|
}
|
|
if len(k.ChainCode) != 32 {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: crypto-hdkey chain code is %d bytes, expected 32", len(k.ChainCode))
|
|
}
|
|
var net *chaincfg.Params
|
|
switch n := k.UseInfo.Network; n {
|
|
case mainnet:
|
|
net = &chaincfg.MainNetParams
|
|
case testnet:
|
|
net = &chaincfg.TestNet3Params
|
|
default:
|
|
return KeyDescriptor{}, fmt.Errorf("ur: unknown coininfo network %d", n)
|
|
}
|
|
comps, err := parseKeypath(k.Origin.Components)
|
|
if err != nil {
|
|
return KeyDescriptor{}, err
|
|
}
|
|
var devPath Path
|
|
for _, d := range comps {
|
|
if d.Type != ChildDerivation {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: wildcards or ranges not allowed in origin path")
|
|
}
|
|
idx := d.Index
|
|
if d.Hardened {
|
|
idx += hdkeychain.HardenedKeyStart
|
|
}
|
|
devPath = append(devPath, idx)
|
|
}
|
|
depth := k.Origin.Depth
|
|
if depth != 0 && int(depth) != len(devPath) {
|
|
return KeyDescriptor{}, fmt.Errorf("ur: origin depth is %d but expected %d", depth, len(devPath))
|
|
}
|
|
return KeyDescriptor{
|
|
Network: net,
|
|
MasterFingerprint: k.Origin.Fingerprint,
|
|
DerivationPath: devPath,
|
|
Children: children,
|
|
KeyData: k.KeyData,
|
|
ChainCode: k.ChainCode,
|
|
ParentFingerprint: k.ParentFingerprint,
|
|
}, nil
|
|
}
|
|
|
|
func parseOutputDescriptor(mode cbor.DecMode, enc []byte) (OutputDescriptor, error) {
|
|
var tags []uint64
|
|
for {
|
|
var raw cbor.RawTag
|
|
if err := mode.Unmarshal(enc, &raw); err != nil {
|
|
break
|
|
}
|
|
tags = append(tags, raw.Number)
|
|
enc = raw.Content
|
|
}
|
|
if len(tags) == 0 {
|
|
return OutputDescriptor{}, errors.New("ur: missing descriptor tag")
|
|
}
|
|
var desc OutputDescriptor
|
|
first := tags[0]
|
|
tags = tags[1:]
|
|
switch first {
|
|
case tagSH:
|
|
desc.Script = P2SH
|
|
if len(tags) == 0 {
|
|
break
|
|
}
|
|
switch tags[0] {
|
|
case tagWSH:
|
|
desc.Script = P2SH_P2WSH
|
|
tags = tags[1:]
|
|
case tagWPKH:
|
|
desc.Script = P2SH_P2WPKH
|
|
tags = tags[1:]
|
|
}
|
|
case tagP2PKH:
|
|
desc.Script = P2PKH
|
|
case tagTR:
|
|
desc.Script = P2TR
|
|
case tagWSH:
|
|
desc.Script = P2WSH
|
|
case tagWPKH:
|
|
desc.Script = P2WPKH
|
|
default:
|
|
return OutputDescriptor{}, fmt.Errorf("ur: unknown script type tag: %d", first)
|
|
}
|
|
if len(tags) == 0 {
|
|
return OutputDescriptor{}, errors.New("ur: missing descriptor script tag")
|
|
}
|
|
funcNumber := tags[0]
|
|
tags = tags[1:]
|
|
if len(tags) > 0 {
|
|
return OutputDescriptor{}, errors.New("ur: extra tags")
|
|
}
|
|
switch funcNumber {
|
|
case tagHDKey: // singlesig
|
|
desc.Type = Singlesig
|
|
k, err := parseHDKey(enc)
|
|
if err != nil {
|
|
return OutputDescriptor{}, err
|
|
}
|
|
desc.Threshold = 1
|
|
desc.Keys = append(desc.Keys, k)
|
|
case tagSortedMulti:
|
|
desc.Type = SortedMulti
|
|
var m multi
|
|
if err := mode.Unmarshal(enc, &m); err != nil {
|
|
return OutputDescriptor{}, err
|
|
}
|
|
desc.Threshold = m.Threshold
|
|
for _, k := range m.Keys {
|
|
keyDesc, err := parseHDKey([]byte(k))
|
|
if err != nil {
|
|
return OutputDescriptor{}, err
|
|
}
|
|
desc.Keys = append(desc.Keys, keyDesc)
|
|
}
|
|
default:
|
|
return desc, fmt.Errorf("unknown script function tag: %d", funcNumber)
|
|
}
|
|
return desc, nil
|
|
}
|
|
|
|
func parseKeypath(comp []any) ([]Derivation, error) {
|
|
if len(comp)%2 == 1 {
|
|
return nil, errors.New("odd number of components")
|
|
}
|
|
var path []Derivation
|
|
for i := 0; i < len(comp); i += 2 {
|
|
d, h := comp[i], comp[i+1]
|
|
var deriv Derivation
|
|
switch d := d.(type) {
|
|
case uint64:
|
|
if d > math.MaxUint32 {
|
|
return nil, errors.New("child index out of range")
|
|
}
|
|
deriv = Derivation{
|
|
Type: ChildDerivation,
|
|
Index: uint32(d),
|
|
}
|
|
case []any:
|
|
switch len(d) {
|
|
case 0:
|
|
deriv = Derivation{
|
|
Type: WildcardDerivation,
|
|
}
|
|
case 2:
|
|
start, ok1 := d[0].(uint64)
|
|
end, ok2 := d[1].(uint64)
|
|
if !ok1 || !ok2 || start > math.MaxUint32 || end > math.MaxUint32 {
|
|
return nil, errors.New("invalid range derivation")
|
|
}
|
|
deriv = Derivation{
|
|
Type: RangeDerivation,
|
|
Index: uint32(start),
|
|
End: uint32(end),
|
|
}
|
|
default:
|
|
return nil, errors.New("invalid wildcard derivation")
|
|
}
|
|
default:
|
|
return nil, errors.New("unknown component type")
|
|
}
|
|
hardened, ok := h.(bool)
|
|
if !ok {
|
|
return nil, errors.New("invalid hardened flag")
|
|
}
|
|
deriv.Hardened = hardened
|
|
path = append(path, deriv)
|
|
}
|
|
return path, nil
|
|
}
|