Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 09cc735

Browse files
author
shadowy-pycoder
committed
Added CLI commands for signing and verification
1 parent fb2a909 commit 09cc735

File tree

3 files changed

+273
-75
lines changed

3 files changed

+273
-75
lines changed

‎bmt/main.go‎

Lines changed: 266 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import (
88
"encoding/base64"
99
"encoding/binary"
1010
"encoding/hex"
11-
"errors"
11+
"flag"
1212
"fmt"
1313
"hash"
1414
"math/big"
15-
"sync"
16-
"time"
15+
"os"
16+
"slices"
17+
"strings"
18+
"syscall"
1719

1820
"github.com/btcsuite/btcd/btcutil/bech32"
1921
"github.com/btcsuite/golangcrypto/ripemd160"
2022
"github.com/mr-tron/base58"
23+
"golang.org/x/term"
2124
)
2225

2326
var (
@@ -44,7 +47,7 @@ var (
4447
GenPoint: NewJacobianPoint(genPointX, genPointY, one),
4548
}
4649
identityPoint = NewJacobianPoint(pCurve, zero, one)
47-
addressTypes = [3]string{"legacy", "nested", "segwit"}
50+
addressTypes = []string{"legacy", "nested", "segwit"}
4851
headers = [5][4]byte{
4952
{0x1b, 0x1c, 0x1d, 0x1e}, // 27 - 30 P2PKH uncompressed
5053
{0x1f, 0x20, 0x21, 0x22}, // 31 - 34 P2PKH compressed
@@ -53,6 +56,70 @@ var (
5356
{0x2b, 0x2c, 0x2d, 0x2e}, // TODO 43 - 46 P2TR
5457
}
5558
OutOfRangeError = &PrivateKeyError{Message: "scalar is out of range"}
59+
flags = flag.NewFlagSet("bitcoin message tool", flag.ExitOnError)
60+
usagePrefix = `
61+
██████╗ ███╗ ███╗████████╗
62+
██╔══██╗████╗ ████║╚══██╔══╝
63+
██████╔╝██╔████╔██║ ██║
64+
██╔══██╗██║╚██╔╝██║ ██║
65+
██████╔╝██║ ╚═╝ ██║ ██║
66+
╚═════╝ ╚═╝ ╚═╝ ╚═╝
67+
68+
Bitcoin Message Tool by shadowy-pycoder
69+
70+
GitHub: https://github.com/shadowy-pycoder
71+
72+
Usage: bmt [OPTIONS] COMMAND
73+
Options:
74+
`
75+
usageCommands = `
76+
Commands:
77+
78+
sign Create bitcoin message
79+
verify Verify bitcoin message
80+
`
81+
signUsagePrefix = `Usage bmt sign [-h] -p -a {legacy, nested, segwit} -m [MESSAGE ...] [-d] [-e]
82+
Options:
83+
`
84+
signUsageExamples = `
85+
Examples:
86+
87+
Deterministic signature for compressed private key and legacy address
88+
89+
bmt sign -p -a legacy -d -m "ECDSA is the most fun I have ever experienced"
90+
PrivateKey (WIF): L3V9AFB763LKWWsMh8CyosSG8QV8KDTjYeXqkt4WX5Xyz2aNqLAY
91+
-----BEGIN BITCOIN SIGNED MESSAGE-----
92+
ECDSA is the most fun I have ever experienced
93+
-----BEGIN BITCOIN SIGNATURE-----
94+
16wrm6zJek6REbxbJSLsBHehn3Lj1vo57t
95+
96+
H3x5bM2MpXK9MyLLbIGWQjZQNTP6lfuIjmPqMrU7YZ5CCm5bS9L+zCtrfIOJaloDb0mf9QBSEDIs4UCd/jou1VI=
97+
-----END BITCOIN SIGNATURE-----
98+
`
99+
verifyUsagePrefix = `Usage bmt verify [-h] -a ADDRESS -m [MESSAGE ...] -s SIGNATURE [-e] [-v] [-r]
100+
Options:
101+
`
102+
verifyUsageExamples = `
103+
Examples:
104+
105+
Message verification in verbose mode
106+
107+
bmt verify -a 175A5YsPUdM71mnNCC3i8faxxYJgBonjWL \
108+
-m "ECDSA is the most fun I have ever experienced" \
109+
-s HyiLDcQQ1p2bKmyqM0e5oIBQtKSZds4kJQ+VbZWpr0kYA6Qkam2MlUeTr+lm1teUGHuLapfa43JjyrRqdSA0pxs= \
110+
-v
111+
true
112+
message verified to be from 175A5YsPUdM71mnNCC3i8faxxYJgBonjWL
113+
114+
Display a recovered public key
115+
116+
bmt verify -a 175A5YsPUdM71mnNCC3i8faxxYJgBonjWL \
117+
-m "ECDSA is the most fun I have ever experienced" \
118+
-s HyiLDcQQ1p2bKmyqM0e5oIBQtKSZds4kJQ+VbZWpr0kYA6Qkam2MlUeTr+lm1teUGHuLapfa43JjyrRqdSA0pxs= \
119+
-r
120+
true
121+
024aeaf55040fa16de37303d13ca1dde85f4ca9baa36e2963a27a1c0c1165fe2b1
122+
`
56123
)
57124

58125
type PrivateKeyError struct {
@@ -410,6 +477,9 @@ func (k *PrivateKey) SplitBytes() (version []byte, payload []byte, checkSum []by
410477
return nil, nil, nil, err
411478
}
412479
pkLen := len(privkey)
480+
if pkLen-4 < 1 {
481+
return nil, nil, nil, &PrivateKeyError{Message: "too short"}
482+
}
413483
return privkey[:1], privkey[1 : pkLen-4], privkey[pkLen-4:], nil
414484
}
415485

@@ -1103,83 +1173,204 @@ func SignMessage(pk *PrivateKey, addrType, message string, deterministic, electr
11031173
return nil, &SignatureError{Message: "invalid signature parameters"}
11041174
}
11051175

1106-
func main() {
1176+
func printMessage(bm *BitcoinMessage) {
1177+
fmt.Println("-----BEGIN BITCOIN SIGNED MESSAGE-----")
1178+
fmt.Println(bm.Data)
1179+
fmt.Println("-----BEGIN BITCOIN SIGNATURE-----")
1180+
fmt.Println(bm.Address)
1181+
fmt.Println()
1182+
fmt.Println(string(bm.Signature))
1183+
fmt.Println("-----END BITCOIN SIGNATURE-----")
1184+
}
1185+
1186+
func NewSignCommand() *SignCommand {
1187+
sc := &SignCommand{
1188+
fs: flag.NewFlagSet("sign", flag.ContinueOnError),
1189+
}
1190+
sc.fs.BoolVar(&sc.help, "h", false, "show this help message and exit")
1191+
sc.fs.BoolVar(&sc.deterministic, "d", false, "sign deterministically (RFC6979)")
1192+
sc.fs.BoolVar(&sc.electrum, "e", false, "create electrum-like signature")
1193+
sc.fs.StringVar(&sc.message, "m", "", "[MESSAGE ...] message to sign")
1194+
sc.fs.BoolFunc("p", "private key in wallet import format (WIF)", func(flagValue string) error {
1195+
fmt.Print("PrivateKey (WIF): ")
1196+
1197+
bytepw, err := term.ReadPassword(syscall.Stdin)
1198+
if err != nil {
1199+
os.Exit(1)
1200+
}
1201+
pk := string(bytepw)
1202+
sc.pk = pk
1203+
fmt.Println()
1204+
return nil
1205+
})
1206+
sc.fs.Func("a", "type of bitcoin address (legacy, nested, segwit)", func(flagValue string) error {
1207+
flagValue = strings.ToLower(flagValue)
1208+
if !slices.Contains(addressTypes, flagValue) {
1209+
fmt.Fprintf(os.Stderr, "bmt: invalid argument '%s' for -a flag. See 'bmt sign -h'\n", flagValue)
1210+
os.Exit(2)
1211+
}
1212+
sc.addrType = flagValue
1213+
return nil
1214+
})
1215+
1216+
return sc
1217+
}
1218+
1219+
type SignCommand struct {
1220+
fs *flag.FlagSet
1221+
1222+
help bool
1223+
pk string
1224+
addrType string
1225+
deterministic bool
1226+
electrum bool
1227+
message string
1228+
}
11071229

1108-
//key := big.NewInt(1000)
1109-
start := time.Now()
1110-
// raw, _ := createRawPubKey(key)
1111-
// pk := createPubKey(raw, false)
1112-
// fmt.Println(createAddress(pk))
1113-
// fmt.Println(createNestedSegwit(pk))
1114-
// fmt.Println(hex.EncodeToString([]byte{5, 00, 20}))
1115-
// addr, _ := createNativeSegwit(pk)
1116-
// fmt.Println(addr)
1117-
// fmt.Printf("uint64: %v\n", uint64(18446744073709551615))
1118-
1119-
// fmt.Println(hex.EncodeToString(varInt(12356333474345788523)))
1120-
// fmt.Println(hex.EncodeToString(msgMagic("语言处理")))
1121-
// a := secp256k1.GenPoint
1122-
// b := identityPoint
1123-
// fmt.Println(!a.Eq(b))
1124-
// fmt.Printf("%p%p\n", a.X, &secp256k1.GenPoint.X)
1125-
1126-
// fmt.Println(deriveAddress(pk, "segwit"))
1127-
// fmt.Printf("%p\n", secp256k1.GenPoint.X)
1128-
// fmt.Println(secp256k1.GenPoint)
1129-
// fmt.Printf("%p\n", secp256k1.GenPoint.X)
1130-
// fmt.Println(secp256k1.GenPoint)
1131-
// var pr JacobianPoint
1132-
// fmt.Println(pr.Mul(key, pr.Mul(key, secp256k1.GenPoint)))
1133-
// fmt.Println(secp256k1.GenPoint)
1134-
// message := &BitcoinMessage{Address: "17PDgca9K59ed6RDtXaqje2gWUr8aLvbig",
1135-
// Data: "ECDSA is the most fun I have ever experienced",
1136-
// Signature: []byte("IPxvWLg99SQxAXQd8/cdhR7x1kShMvF3Fw999+hNio2hEuvKH80FAO2YhiatHvt4ugdyB2Yo1NiZzcBm7ZLg5/w="),
1137-
// }
1138-
// fmt.Println(VerifyMessage(message, false))
1139-
//ppkk, _ := new(big.Int).SetString("f2693ef943df4b25eec8310ec3ea4bc146e084f5c524b1ea7cca390e1469c46b", 16)
1140-
w := "L38WQxF2njcyvgjAZ1f4n1cN7yp793VEopHaxav8er7HN5JLeriu"
1141-
sd, err := NewPrivateKey(nil, &w)
1230+
func (sc *SignCommand) Name() string {
1231+
return sc.fs.Name()
1232+
}
1233+
1234+
func (sc *SignCommand) Init(args []string) error {
1235+
sc.fs.Usage = func() {
1236+
fmt.Println(signUsagePrefix)
1237+
sc.fs.PrintDefaults()
1238+
fmt.Println(signUsageExamples)
1239+
}
1240+
return sc.fs.Parse(args)
1241+
}
1242+
1243+
func (sc *SignCommand) Run() error {
1244+
if sc.help {
1245+
sc.fs.Usage()
1246+
os.Exit(0)
1247+
}
1248+
required := []string{"p", "a", "m"}
1249+
seen := make(map[string]bool)
1250+
sc.fs.Visit(func(f *flag.Flag) { seen[f.Name] = true })
1251+
for _, req := range required {
1252+
if !seen[req] {
1253+
return fmt.Errorf("bmt: missing required -%s flag. See 'bmt sign -h'", req)
1254+
}
1255+
}
1256+
pk, err := NewPrivateKey(nil, &sc.pk)
11421257
if err != nil {
1143-
fmt.Println(err)
1144-
}
1145-
fmt.Println(sd.raw)
1146-
var wg sync.WaitGroup
1147-
for range 10 {
1148-
wg.Add(1)
1149-
go func() {
1150-
defer wg.Done()
1151-
ssd, err := NewPrivateKey(nil, nil)
1152-
if err != nil {
1153-
panic(err)
1154-
}
1155-
msg, err := SignMessage(ssd, "legacy", "ECDSA is the most fun I have ever experienced", true, false)
1156-
if err != nil {
1157-
panic(err)
1158-
}
1159-
fmt.Println(string(msg.Signature))
1258+
return fmt.Errorf("bmt: invalid private key: %w", err)
1259+
}
1260+
bm, err := SignMessage(pk, sc.addrType, sc.message, sc.deterministic, sc.electrum)
1261+
if err != nil {
1262+
return fmt.Errorf("bmt: failed signing message: %w", err)
1263+
}
1264+
printMessage(bm)
1265+
return nil
1266+
}
11601267

1161-
_, _, _, err = VerifyMessage(msg, false)
1162-
if err != nil {
1163-
panic(err)
1164-
}
1268+
func NewVerifyCommand() *VerifyCommand {
1269+
vc := &VerifyCommand{
1270+
fs: flag.NewFlagSet("verify", flag.ContinueOnError),
1271+
}
1272+
vc.message = &BitcoinMessage{}
1273+
vc.fs.BoolVar(&vc.help, "h", false, "show this help message and exit")
1274+
vc.fs.BoolVar(&vc.electrum, "e", false, "verify electrum-like signature")
1275+
vc.fs.BoolVar(&vc.recpub, "r", false, "recover public key")
1276+
vc.fs.BoolVar(&vc.verbose, "v", false, "show full message")
1277+
vc.fs.StringVar(&vc.message.Address, "a", "", "ADDRESS bitcoin address")
1278+
vc.fs.StringVar(&vc.message.Data, "m", "", "[MESSAGE ...] message to verify")
1279+
vc.fs.Func("s", "SIGNATURE bitcoin signature in base64 format", func(flagValue string) error {
1280+
vc.message.Signature = []byte(flagValue)
1281+
return nil
1282+
})
1283+
return vc
1284+
}
11651285

1166-
//fmt.Println(verified, pub, message)
1167-
}()
1286+
type VerifyCommand struct {
1287+
fs *flag.FlagSet
1288+
1289+
help bool
1290+
electrum bool
1291+
recpub bool
1292+
verbose bool
1293+
message *BitcoinMessage
1294+
}
1295+
1296+
func (vc *VerifyCommand) Name() string {
1297+
return vc.fs.Name()
1298+
}
11681299

1300+
func (vc *VerifyCommand) Init(args []string) error {
1301+
vc.fs.Usage = func() {
1302+
fmt.Println(verifyUsagePrefix)
1303+
vc.fs.PrintDefaults()
1304+
fmt.Println(verifyUsageExamples)
11691305
}
1170-
wg.Wait()
1171-
fmt.Println(bitsToInt([]byte("Hello jshakdhasjkdhaskjdhkasjhdkjashdjkashdjks"), 1123))
1172-
fmt.Println(hex.EncodeToString(intToOct(big.NewInt(65533), 3)))
1173-
fmt.Println(hex.EncodeToString(bitsToOct([]byte("Hello jshakdhasjkdhaskjdhkasjhdkjashdjkashdjks"), big.NewInt(6533), 36, 12)))
1174-
fmt.Println(2 + 3>>2)
1175-
var d big.Int
1176-
fmt.Println(d.Add(two, three).Rsh(&d, 2))
1177-
fmt.Println(rfcSign(big.NewInt(6553365533564754), big.NewInt(65533)))
1178-
_, err = NewPrivateKey(big.NewInt(1000), &w)
1179-
fmt.Println(errors.Unwrap(err))
1306+
return vc.fs.Parse(args)
1307+
}
11801308

1181-
_, _, _, err = VerifyMessage(&BitcoinMessage{Address: "ss", Data: "dddf", Signature: []byte("Gdggdgd")}, false)
1182-
//var dddd *SignatureError
1183-
fmt.Println(time.Since(start))
1309+
func (vc *VerifyCommand) Run() error {
1310+
if vc.help {
1311+
vc.fs.Usage()
1312+
os.Exit(0)
1313+
}
1314+
required := []string{"s", "a", "m"}
1315+
seen := make(map[string]bool)
1316+
vc.fs.Visit(func(f *flag.Flag) { seen[f.Name] = true })
1317+
for _, req := range required {
1318+
if !seen[req] {
1319+
return fmt.Errorf("bmt: missing required -%s flag. See 'bmt verify -h'", req)
1320+
}
1321+
}
1322+
verified, pubkey, result, err := VerifyMessage(vc.message, vc.electrum)
1323+
if err != nil {
1324+
return fmt.Errorf("bmt: failed verifying message: %w", err)
1325+
}
1326+
fmt.Println(verified)
1327+
if vc.verbose {
1328+
fmt.Println(result)
1329+
}
1330+
if vc.recpub {
1331+
fmt.Println(pubkey)
1332+
}
1333+
return nil
1334+
}
11841335

1336+
type Runner interface {
1337+
Init([]string) error
1338+
Run() error
1339+
Name() string
1340+
}
1341+
1342+
func root(args []string) error {
1343+
flags.Usage = func() {
1344+
fmt.Println(usagePrefix)
1345+
flags.PrintDefaults()
1346+
fmt.Println(usageCommands)
1347+
}
1348+
flags.Bool("h", false, "show this help message and exit")
1349+
1350+
if len(args) < 1 || slices.Contains([]string{"-h", "--h"}, args[0]) {
1351+
flags.Usage()
1352+
os.Exit(0)
1353+
}
1354+
cmds := []Runner{
1355+
NewSignCommand(),
1356+
NewVerifyCommand(),
1357+
}
1358+
1359+
subcommand := args[0]
1360+
1361+
for _, cmd := range cmds {
1362+
if cmd.Name() == subcommand {
1363+
cmd.Init(os.Args[2:])
1364+
return cmd.Run()
1365+
}
1366+
}
1367+
return fmt.Errorf("bmt: '%s' is not a bmt command. See 'bmt -h'", subcommand)
1368+
}
1369+
1370+
func main() {
1371+
1372+
if err := root(os.Args[1:]); err != nil {
1373+
fmt.Fprintln(os.Stderr, err)
1374+
os.Exit(2)
1375+
}
11851376
}

‎go.mod‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ require (
66
github.com/btcsuite/btcd/btcutil v1.1.5
77
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8
88
github.com/mr-tron/base58 v1.2.0
9+
golang.org/x/term v0.22.0
910
)
11+
12+
require golang.org/x/sys v0.22.0 // indirect

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /