seedhammer-v1-companion/address/address.go
mineracks 9261cf368a Lift composer substrate from upstream v1.3.0
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>
2026-05-28 18:36:40 +10:00

158 lines
4.4 KiB
Go

// package address derives recieve and change addresses from
// output descriptors.
package address
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"sort"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/mineracks/seedhammer-v1-companion/bc/urtypes"
)
func Change(desc urtypes.OutputDescriptor, index uint32) (string, error) {
return address(desc, index, true)
}
func Receive(desc urtypes.OutputDescriptor, index uint32) (string, error) {
return address(desc, index, false)
}
func Supported(desc urtypes.OutputDescriptor) bool {
_, err := Receive(desc, 0)
return !errors.Is(err, errUnsupported)
}
var errUnsupported = errors.New("unsupported descriptor")
func address(desc urtypes.OutputDescriptor, index uint32, change bool) (string, error) {
var addr btcutil.Address
var network *chaincfg.Params
switch desc.Type {
case urtypes.SortedMulti:
var keys []*btcutil.AddressPubKey
for _, k := range desc.Keys {
pub, err := derivePubKey(k, index, change)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
if network != nil && k.Network != network {
return "", fmt.Errorf("address: multisig descriptor mixes networks: %w", errUnsupported)
}
network = k.Network
addrPub, err := btcutil.NewAddressPubKey(pub.SerializeCompressed(), network)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
keys = append(keys, addrPub)
}
sort.Slice(keys, func(i, j int) bool {
return bytes.Compare(keys[i].PubKey().SerializeCompressed(), keys[j].PubKey().SerializeCompressed()) == -1
})
script, err := txscript.MultiSigScript(keys, desc.Threshold)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
switch desc.Script {
case urtypes.P2SH:
addr, err = btcutil.NewAddressScriptHash(script, network)
case urtypes.P2WSH, urtypes.P2SH_P2WSH:
hash := sha256.Sum256(script)
addr, err = btcutil.NewAddressWitnessScriptHash(hash[:], network)
default:
return "", fmt.Errorf("address: multisig script: %s: %w", desc.Script, errUnsupported)
}
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
case urtypes.Singlesig:
k := desc.Keys[0]
network = k.Network
pub, err := derivePubKey(k, index, change)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
switch desc.Script {
case urtypes.P2PKH:
pkHash := btcutil.Hash160(pub.SerializeCompressed())
addr, err = btcutil.NewAddressPubKeyHash(pkHash, network)
case urtypes.P2WPKH, urtypes.P2SH_P2WPKH:
pkHash := btcutil.Hash160(pub.SerializeCompressed())
addr, err = btcutil.NewAddressWitnessPubKeyHash(pkHash, network)
case urtypes.P2TR:
tkey := txscript.ComputeTaprootKeyNoScript(pub)
addr, err = btcutil.NewAddressTaproot(schnorr.SerializePubKey(tkey), network)
default:
return "", fmt.Errorf("address: singlesig script: %s: %w", desc.Script, errUnsupported)
}
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
default:
return "", fmt.Errorf("address: descriptor: %w", errUnsupported)
}
// Derive wrapped address types.
switch desc.Script {
case urtypes.P2SH_P2WPKH, urtypes.P2SH_P2WSH:
script, err := txscript.PayToAddrScript(addr)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
addr, err = btcutil.NewAddressScriptHash(script, network)
if err != nil {
return "", fmt.Errorf("address: %w", err)
}
}
return addr.String(), nil
}
func derivePubKey(k urtypes.KeyDescriptor, index uint32, change bool) (*secp256k1.PublicKey, error) {
children := k.Children
if len(children) == 0 {
// Default to <0;1>/*.
children = append(children,
urtypes.Derivation{
Type: urtypes.RangeDerivation,
Index: 0,
End: 1,
},
urtypes.Derivation{
Type: urtypes.WildcardDerivation,
},
)
}
xpub := k.ExtendedKey()
for _, c := range children {
var id uint32
switch c.Type {
case urtypes.ChildDerivation:
id = c.Index
case urtypes.RangeDerivation:
if c.End != c.Index+1 {
return nil, errors.New("unsupported range path element")
}
id = c.Index
if change {
id = c.End
}
case urtypes.WildcardDerivation:
id = index
default:
return nil, errors.New("unsupported path element")
}
child, err := xpub.Derive(id)
if err != nil {
return nil, err
}
xpub = child
}
return xpub.ECPubKey()
}