Skip to content

Commit

Permalink
ed25519 curve support
Browse files Browse the repository at this point in the history
The implementation is based on the SLIP-0010 specification.

https://github.com/satoshilabs/slips/blob/master/slip-0010.md
  • Loading branch information
regeda committed Feb 14, 2022
1 parent a6365d0 commit a3ff8f7
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 41 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ name: build-check
on: [push, pull_request]
jobs:
build-check:
name: build-check Go ${{ matrix.go }}
runs-on: ubuntu-20.04
name: Go Test
runs-on: ubuntu-latest
strategy:
matrix:
go:
- '1.17'
- '1.16'
- '1.15'
- '1.14'
- '1.13'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go }}
- run: make build_check
test-coverage:
name: build-check Go ${{ matrix.go }}
runs-on: ubuntu-20.04
name: Go Test With Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage.out
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

[![Build check](https://github.com/tyler-smith/go-bip32/workflows/build-check/badge.svg?branch=master)](https://github.com/tyler-smith/go-bip32/actions?query=workflow%3Abuild-check+branch%3Amaster)
[![Go Report Card](https://goreportcard.com/badge/github.com/tyler-smith/go-bip32)](https://goreportcard.com/report/github.com/tyler-smith/go-bip32)
[![Coverage Status](https://coveralls.io/repos/github/tyler-smith/go-bip32/badge.svg?branch=TS_v2)](https://coveralls.io/github/tyler-smith/go-bip32?branch=TS_v2)
[![Coverage Status](https://coveralls.io/repos/github/tyler-smith/go-bip32/badge.svg)](https://coveralls.io/github/tyler-smith/go-bip32)

An implementation of the BIP32 spec for Hierarchical Deterministic Bitcoin addresses as a simple Go library. The semantics of derived keys are up to the user. [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) are good schemes to implement with this library. An additional library for either or both of those on top of this library should be developed.

The library supports "Bitcoin" and "ed25519" curves for [Universal private key derivation from master private key](https://github.com/satoshilabs/slips/blob/master/slip-0010.md).

## Example

It's very unlikely, but possible, that a given index does not produce a valid
It's very unlikely, but possible, that a given index does not produce a valid
private key. Error checking is skipped in this example for brevity but should be handled in real code. In such a case, a ErrInvalidPrivateKey is returned.

ErrInvalidPrivateKey should be handled by trying the next index for a child key.
Expand Down
100 changes: 76 additions & 24 deletions bip32.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package bip32

import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"errors"
)
Expand Down Expand Up @@ -42,28 +40,54 @@ var (

// ErrInvalidPublicKey is returned when a derived public key is invalid
ErrInvalidPublicKey = errors.New("Invalid public key")

// ErrUnsupportedEd25519PublicKeyDerivation is returned when a public child key is derived with ed25519 curve.
ErrUnsupportedEd25519PublicKeyDerivation = errors.New("Public key for ed25519 is not supported for normal derivation")
)

// The supported curves.
// @link SupportedCurves
const (
Bitcoin Curve = iota // secp256k1
Ed25519
)

// curvesSalt corresponds to the supported curves @SupportedCurves by the index.
var curvesSalt = [][]byte{
[]byte("Bitcoin seed"),
[]byte("ed25519 seed"),
}

// Curve defines the private key for the HMAC hash that generates the master key.
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#master-key-generation
type Curve byte

// Key represents a bip32 extended key
type Key struct {
Key []byte // 33 bytes
Version []byte // 4 bytes
ChildNumber []byte // 4 bytes
FingerPrint []byte // 4 bytes
ChainCode []byte // 32 bytes
Depth byte // 1 bytes
Depth byte // 1 byte
IsPrivate bool // unserialized

// The Deserialize function sets it bip32.Bitcoin by default.
// see https://github.com/satoshilabs/slips/blob/master/slip-0132.md#registered-hd-version-bytes
curve Curve
}

// NewMasterKey creates a new master extended key from a seed
func NewMasterKey(seed []byte) (*Key, error) {
// NewMasterKeyWithCurve creates a new master extended key from a seed
// with a given curve algorithm.
func NewMasterKeyWithCurve(seed []byte, curve Curve) (*Key, error) {
if int(curve) > len(curvesSalt) {
panic("unsupported curve, only bip32.Bitcoin and bit32.Ed25519 are supported")
}
// Generate key and chaincode
hmac := hmac.New(sha512.New, []byte("Bitcoin seed"))
_, err := hmac.Write(seed)
intermediary, err := hmac512(seed, curvesSalt[curve])
if err != nil {
return nil, err
}
intermediary := hmac.Sum(nil)

// Split it into our key and chain code
keyBytes := intermediary[:32]
Expand All @@ -84,18 +108,35 @@ func NewMasterKey(seed []byte) (*Key, error) {
ChildNumber: []byte{0x00, 0x00, 0x00, 0x00},
FingerPrint: []byte{0x00, 0x00, 0x00, 0x00},
IsPrivate: true,
curve: curve,
}

return key, nil
}

// NewMasterKey creates a new master extended key from a seed
// using the Bitcoin curve.
func NewMasterKey(seed []byte) (*Key, error) {
return NewMasterKeyWithCurve(seed, Bitcoin)
}

// NewChildKey derives a child key from a given parent as outlined by bip32
func (key *Key) NewChildKey(childIdx uint32) (*Key, error) {
// Fail early if trying to create hardned child from public key
if !key.IsPrivate && childIdx >= FirstHardenedChild {
return nil, ErrHardnedChildPublicKey
}

if key.curve == Ed25519 {
if !key.IsPrivate {
return nil, ErrUnsupportedEd25519PublicKeyDerivation
}
// With ed25519 curve all derivation-path indexes will be promoted to hardened indexes.
if childIdx < FirstHardenedChild {
childIdx += FirstHardenedChild
}
}

intermediary, err := key.getIntermediary(childIdx)
if err != nil {
return nil, err
Expand All @@ -107,26 +148,37 @@ func (key *Key) NewChildKey(childIdx uint32) (*Key, error) {
ChainCode: intermediary[32:],
Depth: key.Depth + 1,
IsPrivate: key.IsPrivate,
curve: key.curve,
}

// Bip32 CKDpriv
if key.IsPrivate {
childKey.Version = PrivateWalletVersion
fingerprint, err := hash160(publicKeyForPrivateKey(key.Key))
if err != nil {
return nil, err

var publicKey []byte

// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key
if childKey.curve == Ed25519 {
childKey.Key = intermediary[:32]
publicKey = publicKeyForPrivateKeyEd25519(key.Key)
} else {
childKey.Key = addPrivateKeys(intermediary[:32], key.Key)
// Validate key
if err := validatePrivateKey(childKey.Key); err != nil {
return nil, err
}
publicKey = publicKeyForPrivateKeyBitcoin(key.Key)
}
childKey.FingerPrint = fingerprint[:4]
childKey.Key = addPrivateKeys(intermediary[:32], key.Key)

// Validate key
err = validatePrivateKey(childKey.Key)
fingerprint, err := hash160(publicKey)
if err != nil {
return nil, err
}
childKey.FingerPrint = fingerprint[:4]

// Bip32 CKDpub
} else {
keyBytes := publicKeyForPrivateKey(intermediary[:32])
keyBytes := publicKeyForPrivateKeyBitcoin(intermediary[:32])

// Validate key
err := validateChildPublicKey(keyBytes)
Expand Down Expand Up @@ -157,19 +209,14 @@ func (key *Key) getIntermediary(childIdx uint32) ([]byte, error) {
data = append([]byte{0x0}, key.Key...)
} else {
if key.IsPrivate {
data = publicKeyForPrivateKey(key.Key)
data = publicKeyForPrivateKeyBitcoin(key.Key)
} else {
data = key.Key
}
}
data = append(data, childIndexBytes...)

hmac := hmac.New(sha512.New, key.ChainCode)
_, err := hmac.Write(data)
if err != nil {
return nil, err
}
return hmac.Sum(nil), nil
return hmac512(data, key.ChainCode)
}

// PublicKey returns the public version of key or return a copy
Expand All @@ -178,7 +225,11 @@ func (key *Key) PublicKey() *Key {
keyBytes := key.Key

if key.IsPrivate {
keyBytes = publicKeyForPrivateKey(keyBytes)
if key.curve == Ed25519 {
keyBytes = publicKeyForPrivateKeyEd25519(keyBytes)
} else {
keyBytes = publicKeyForPrivateKeyBitcoin(keyBytes)
}
}

return &Key{
Expand All @@ -189,6 +240,7 @@ func (key *Key) PublicKey() *Key {
FingerPrint: key.FingerPrint,
ChainCode: key.ChainCode,
IsPrivate: false,
curve: key.curve,
}
}

Expand Down
Loading

0 comments on commit a3ff8f7

Please sign in to comment.