Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mod/bip137sig/bip32.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (

// reference: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki

// ParseDerivationPath parses a BIP-32 path into child indices.
// "m" or "" yields a nil path. A trailing ' or h marks a hardened index,
// setting bit 31 (0x80000000) on the value.
func ParseDerivationPath(path string) ([]uint32, error) {
const hardenedOffset uint32 = 0x80000000

Expand Down
2 changes: 2 additions & 0 deletions mod/bip137sig/bip39.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ func MnemonicToSeed(words []string, passphrase string) (Seed, error) {
return Seed(pbkdf2.Key([]byte(mnemonic), []byte(salt), 2048, SeedLengthBytes, sha512.New)), nil
}

// NewEntropy returns cryptographically random entropy of the given size.
// bits must be 128-256 in 32-bit increments.
func NewEntropy(bits int) (Entropy, error) {
if bits%EntropyStepBits != 0 || bits < MinEntropyBits || bits > MaxEntropyBits {
return nil, fmt.Errorf("invalid entropy size: %d", bits)
Expand Down
1 change: 1 addition & 0 deletions mod/bip137sig/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
MethodSeed = "bip137sig.seed"
)

// Module derives BIP-137 keys from a BIP-39 seed.
type Module interface {
GenerateSeed() (seed Seed, err error)
DeriveKey(seed Seed, path string) (privateKey crypto.PrivateKey, err error)
Expand Down
2 changes: 2 additions & 0 deletions mod/bip137sig/src/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Engine struct {
mod *Module
}

// NewTextSigner returns a BIP-137 signer for the key.
// Only the BIP137 scheme and secp256k1 key type are supported.
func (e Engine) NewTextSigner(key *crypto.PublicKey, scheme string) (crypto.TextSigner, error) {
switch {
case scheme != crypto.SchemeBIP137:
Expand Down
2 changes: 2 additions & 0 deletions mod/bip137sig/src/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (mod *Module) String() string {
return bip137sig.ModuleName
}

// DeriveKey derives a secp256k1 private key from seed along the BIP-32 path on mainnet.
func (mod *Module) DeriveKey(seed bip137sig.Seed, path string) (privateKey crypto.PrivateKey, err error) {
derivationPath, err := bip137sig.ParseDerivationPath(path)
if err != nil {
Expand Down Expand Up @@ -72,6 +73,7 @@ func (mod *Module) DeriveKey(seed bip137sig.Seed, path string) (privateKey crypt
}, nil
}

// GenerateSeed builds a seed from fresh default-strength entropy with an empty passphrase.
func (mod *Module) GenerateSeed() (seed bip137sig.Seed, err error) {
entropy, err := bip137sig.NewEntropy(bip137sig.DefaultEntropyBits)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions mod/bip137sig/src/op_derive_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type opDeriveKeyArgs struct {
Out string `query:"optional"`
}

// OpDeriveKey receives a Seed over the channel and replies with the key derived along Path.
func (mod *Module) OpDeriveKey(
ctx *astral.Context,
q *routing.IncomingQuery,
Expand Down
1 change: 1 addition & 0 deletions mod/bip137sig/src/op_mnemonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type opMnemonicArgs struct {
Out string `query:"optional"`
}

// OpMnemonic receives Entropy over the channel and replies with the space-joined mnemonic words.
func (mod *Module) OpMnemonic(
ctx *astral.Context,
q *routing.IncomingQuery,
Expand Down
1 change: 1 addition & 0 deletions mod/bip137sig/src/op_new_entropy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type opNewEntropyArgs struct {
Out string `query:"optional"`
}

// OpNewEntropy replies with fresh entropy of Bits length, defaulting to DefaultEntropyBits.
func (mod *Module) OpNewEntropy(
ctx *astral.Context,
q *routing.IncomingQuery,
Expand Down
1 change: 1 addition & 0 deletions mod/bip137sig/src/op_seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type opSeedArgs struct {
Out string `query:"optional"`
}

// OpSeed receives a whitespace-separated mnemonic and replies with the seed derived under Passphrase.
func (mod *Module) OpSeed(
ctx *astral.Context,
q *routing.IncomingQuery,
Expand Down
3 changes: 3 additions & 0 deletions mod/coldcard/ckcc/ckcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewDevice(serial string) *Device {
return &Device{Serial: serial}
}

// List enumerates connected Coldcards by parsing the output of the external ckcc CLI.
func List() (devices []*Device, err error) {
cmd := exec.Command("ckcc", "list")

Expand Down Expand Up @@ -46,6 +47,7 @@ func List() (devices []*Device, err error) {
return
}

// PubKey returns the public key at the given derivation path; an empty path defaults to coldcard.BIP44Path.
func (c *Device) PubKey(path string) (string, error) {
if len(path) == 0 {
path = coldcard.BIP44Path
Expand All @@ -65,6 +67,7 @@ func (c *Device) PubKey(path string) (string, error) {
return strings.TrimSpace(stdout.String()), nil
}

// Msg signs msg with the key at the given derivation path; an empty path defaults to coldcard.BIP44Path.
func (c *Device) Msg(msg string, path string) (string, error) {
if len(path) == 0 {
path = coldcard.BIP44Path
Expand Down
4 changes: 4 additions & 0 deletions mod/coldcard/src/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type Engine struct {
mod *Module
}

// NewTextSigner returns a BIP137 signer only for a secp256k1 key whose pubkey
// matches a currently-connected ColdCard; otherwise ErrUnsupported.
func (e *Engine) NewTextSigner(key *crypto.PublicKey, scheme string) (crypto.TextSigner, error) {
switch {
case scheme != "bip137":
Expand All @@ -37,6 +39,8 @@ type MessageSigner struct {
path string
}

// SignText signs on the hardware device; the device returns base64 which is
// decoded into the raw BIP137 signature.
func (m *MessageSigner) SignText(ctx *astral.Context, msg string) (*crypto.Signature, error) {
sigBase64, err := m.dev.Msg(msg, m.path)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions mod/coldcard/src/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func (mod *Module) Run(ctx *astral.Context) error {
return nil
}

// Scan enumerates connected ColdCards and records each serial to pubkey;
// devices whose pubkey cannot be read are skipped.
func (mod *Module) Scan() error {
devices, err := ckcc.List()
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions mod/coldcard/src/op_scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type opScanArgs struct {
Out string `query:"optional"`
}

// OpScan triggers a device rescan and replies over the channel with an Ack on
// success or an Error.
func (mod *Module) OpScan(ctx *astral.Context, q *routing.IncomingQuery, args opScanArgs) (err error) {
ch := channel.New(q.AcceptRaw(), channel.WithFormats(args.In, args.Out))
defer ch.Close()
Expand Down
2 changes: 2 additions & 0 deletions mod/crypto/src/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ func (mod *Module) NewHashSigner(key *crypto.PublicKey, scheme string) (crypto.H
)
}

// NodeSigner returns a signer for the node's own secp256k1 key.
// Panics if no engine can provide one.
func (mod *Module) NodeSigner() crypto.HashSigner {
signer, err := mod.NewHashSigner(&crypto.PublicKey{
Type: "secp256k1",
Expand Down
2 changes: 2 additions & 0 deletions mod/crypto/src/object_holder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

var _ objects.Holder = &Module{}

// HoldObject reports whether the object is an indexed key.
// Fails safe: a lookup error returns true so the object is not dropped.
func (mod *Module) HoldObject(objectID *astral.ObjectID) bool {
held, err := mod.db.isKeyIndexed(objectID)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions mod/crypto/src/op_verify_text_signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type opVerifyTextSignatureArgs struct {
Out string `query:"optional"`
}

// OpVerifyTextSignature accepts a raw channel and verifies a text signature.
// Public key and text may come from query args or be streamed as channel
// messages; a Signature message triggers verification and ends the exchange.
// Defaults the public key to the caller's identity when none is supplied.
func (mod *Module) OpVerifyTextSignature(ctx *astral.Context, q *routing.IncomingQuery, args opVerifyTextSignatureArgs) (err error) {
ch := channel.New(q.AcceptRaw(), channel.WithFormats(args.In, args.Out))
defer ch.Close()
Expand Down
4 changes: 4 additions & 0 deletions mod/secp256k1/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// SignASN1 produces an ASN.1-encoded ECDSA signature over hash.
// Rejects keys whose type is not KeyType.
func SignASN1(key *crypto.PrivateKey, hash []byte) (*crypto.Signature, error) {
switch {
case key.Type != KeyType:
Expand All @@ -30,6 +32,8 @@ func SignASN1(key *crypto.PrivateKey, hash []byte) (*crypto.Signature, error) {
}, nil
}

// VerifyASN1 checks an ASN.1-encoded ECDSA signature over hash.
// Accepts only KeyType keys and the "asn1" scheme; returns ErrInvalidSignature on mismatch.
func VerifyASN1(key *crypto.PublicKey, hash []byte, sig *crypto.Signature) error {
switch {
case key.Type != KeyType:
Expand Down
2 changes: 2 additions & 0 deletions mod/secp256k1/hash_signer_asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// HashSignerASN1 is a crypto.HashSigner that emits ASN.1-encoded ECDSA signatures.
// Holds the decoded private key for repeated signing.
type HashSignerASN1 struct {
key *ecdsa.PrivateKey
}
Expand Down
1 change: 1 addition & 0 deletions mod/secp256k1/src/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (e Engine) DerivePublicKey(ctx *astral.Context, key *crypto.PrivateKey) (*c
return modSecp256k1.PublicKey(key), nil
}

// NewHashSigner only supports secp256k1 keys with the asn1 scheme.
func (e Engine) NewHashSigner(key *crypto.PublicKey, scheme string) (crypto.HashSigner, error) {
switch {
case key.Type != modSecp256k1.KeyType:
Expand Down
1 change: 1 addition & 0 deletions mod/secp256k1/src/op_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type opNewArgs struct {
Out string `query:"optional"`
}

// OpNew generates a fresh secp256k1 key and sends it over the accepted channel.
func (mod *Module) OpNew(ctx *astral.Context, q *routing.IncomingQuery, args opNewArgs) (err error) {
ch := channel.New(q.AcceptRaw(), channel.WithFormats(args.In, args.Out))
defer ch.Close()
Expand Down