Skip to content

Commit 2c98c34

Browse files
Update bsvalias structures and add signature functions.
1 parent b860db8 commit 2c98c34

File tree

6 files changed

+173
-89
lines changed

6 files changed

+173
-89
lines changed

bsvalias/bsvalias.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,11 @@ import (
1010
)
1111

1212
var (
13-
ErrInvalidHandle = errors.New("Invalid handle") // handle is badly formatted
14-
ErrNotCapable = errors.New("Not capable") // host site doesn't support a function
13+
ErrInvalidHandle = errors.New("Invalid handle") // handle is badly formatted
14+
ErrNotCapable = errors.New("Not capable") // host site doesn't support a function
15+
ErrInvalidSignature = errors.New("Invalid signature") // The signature in a request is invalid
1516
)
1617

17-
type Identity struct {
18-
Handle string
19-
Site Site
20-
Alias string
21-
Hostname string
22-
}
23-
2418
func NewIdentity(ctx context.Context, handle string) (Identity, error) {
2519
result := Identity{
2620
Handle: handle,
@@ -50,14 +44,10 @@ func (i *Identity) GetPublicKey(ctx context.Context) (bitcoin.PublicKey, error)
5044
return bitcoin.PublicKey{}, errors.Wrap(err, URLNamePKI)
5145
}
5246

53-
var response struct {
54-
Version string `json:"bsvalias"`
55-
Handle string `json:"handle"`
56-
PublicKey string `json:"pubkey"`
57-
}
58-
5947
url = strings.ReplaceAll(url, "{alias}", i.Alias)
6048
url = strings.ReplaceAll(url, "{domain.tld}", i.Hostname)
49+
50+
var response PublicKeyResponse
6151
if err := get(url, &response); err != nil {
6252
return bitcoin.PublicKey{}, errors.Wrap(err, "http get")
6353
}

bsvalias/bsvalias_test.go

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package bsvalias
22

33
import (
44
"context"
5-
"strconv"
65
"strings"
76
"testing"
87

9-
"github.com/pkg/errors"
108
"github.com/tokenized/pkg/bitcoin"
9+
10+
"github.com/pkg/errors"
1111
)
1212

1313
// Keep commented so we don't hit moneybutton.com on every test run.
@@ -148,14 +148,7 @@ func TestBRFCID(t *testing.T) {
148148
}
149149

150150
func TestMessageSignature(t *testing.T) {
151-
request := struct {
152-
SenderName string
153-
SenderHandle string
154-
DateTime string
155-
Amount uint64
156-
Purpose string
157-
Signature string
158-
}{
151+
request := PaymentDestinationRequest{
159152
SenderName: "Curtis Ellis",
160153
SenderHandle: "loosethinker@moneybutton.com",
161154
DateTime: "2020-06-08T20:25:38.199Z",
@@ -164,24 +157,13 @@ func TestMessageSignature(t *testing.T) {
164157
Signature: "H5+9lO39t20kL5GaGJFjauX9by/o4ljlYRMIIIVKY4JqLFPVMVfVCb8nxPOotSJZUppNsckleoqF2VaylpOQYeI=",
165158
}
166159

167-
hash, err := SignatureHashForMessage(request.SenderHandle +
168-
strconv.FormatUint(request.Amount, 10) + request.DateTime + request.Purpose)
169-
if err != nil {
170-
t.Fatalf("Failed to create sig hash : %s", err)
171-
}
172-
173160
pubKey, err := bitcoin.PublicKeyFromStr("037d391ec99f5fbc48894986391d3d2388045bcf85409ce2e2a92a683dc7a76581")
174161
if err != nil {
175162
t.Fatalf("Failed to parse pub key : %s", err)
176163
}
177164

178-
sig, err := bitcoin.SignatureFromCompact(request.Signature)
179-
if err != nil {
180-
t.Fatalf("Failed to parse sig : %s", err)
181-
}
182-
183-
if !sig.Verify(hash.Bytes(), pubKey) {
184-
t.Fatalf("Failed to verify sig")
165+
if err := request.CheckSignature(pubKey); err != nil {
166+
t.Fatalf("Failed to check sig : %s", err)
185167
}
186168

187169
t.Logf("Signature is valid")

bsvalias/models.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package bsvalias
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/pkg/errors"
8+
"github.com/tokenized/pkg/bitcoin"
9+
"github.com/tokenized/pkg/wire"
10+
)
11+
12+
// Capabilities contains the information about the endpoints supported by the bsvalias host.
13+
type Capabilities struct {
14+
Version string `json:"bsvalias"`
15+
Capabilities map[string]interface{} `json:"capabilities"`
16+
}
17+
18+
// Site represents a bsvalias host.
19+
type Site struct {
20+
Capabilities Capabilities
21+
URL string `json:"url"`
22+
}
23+
24+
// Identity represents a paymail/bsvalias handle and the data retrieved for the host.
25+
type Identity struct {
26+
Handle string
27+
Site Site
28+
Alias string
29+
Hostname string
30+
}
31+
32+
// PublicKeyResponse is the raw response from a PublicKey endpoint.
33+
type PublicKeyResponse struct {
34+
Version string `json:"bsvalias"`
35+
Handle string `json:"handle"`
36+
PublicKey string `json:"pubkey"`
37+
}
38+
39+
// PaymentDestinationRequest is the data structure sent to request a payment destination.
40+
type PaymentDestinationRequest struct {
41+
SenderName string `json:"senderName"`
42+
SenderHandle string `json:"senderHandle"`
43+
DateTime string `json:"dt"`
44+
Amount uint64 `json:"amount"`
45+
Purpose string `json:"purpose"`
46+
Signature string `json:"signature"`
47+
}
48+
49+
// Sign adds a signature to the request. The key should correspond to the sender handle's PKI.
50+
func (r *PaymentDestinationRequest) Sign(key bitcoin.Key) error {
51+
sigHash, err := SignatureHashForMessage(r.SenderHandle + strconv.FormatUint(r.Amount, 10) +
52+
r.DateTime + r.Purpose)
53+
if err != nil {
54+
return errors.Wrap(err, "signature hash")
55+
}
56+
57+
sig, err := key.Sign(sigHash.Bytes())
58+
if err != nil {
59+
return errors.Wrap(err, "sign")
60+
}
61+
62+
r.Signature = sig.ToCompact()
63+
return nil
64+
}
65+
66+
func (r PaymentDestinationRequest) CheckSignature(publicKey bitcoin.PublicKey) error {
67+
sigHash, err := SignatureHashForMessage(r.SenderHandle + strconv.FormatUint(r.Amount, 10) +
68+
r.DateTime + r.Purpose)
69+
if err != nil {
70+
return errors.Wrap(err, "signature hash")
71+
}
72+
73+
sig, err := bitcoin.SignatureFromCompact(r.Signature)
74+
if err != nil {
75+
return errors.Wrap(err, fmt.Sprintf("parse signature: %s", r.Signature))
76+
}
77+
78+
if !sig.Verify(sigHash.Bytes(), publicKey) {
79+
return ErrInvalidSignature
80+
}
81+
82+
return nil
83+
}
84+
85+
// PaymentDestinationResponse is the raw response from a PaymentDestination endpoint.
86+
type PaymentDestinationResponse struct {
87+
Output string `json:"output"`
88+
}
89+
90+
// PaymentRequestRequest is the data structure sent to request a payment request.
91+
type PaymentRequestRequest struct {
92+
SenderName string `json:"senderName"`
93+
SenderHandle string `json:"senderHandle"`
94+
DateTime string `json:"dt"`
95+
AssetID string `json:"assetID"`
96+
Amount uint64 `json:"amount"`
97+
Purpose string `json:"purpose"`
98+
Signature string `json:"signature"`
99+
}
100+
101+
// Sign adds a signature to the request. The key should correspond to the sender handle's PKI.
102+
func (r *PaymentRequestRequest) Sign(key bitcoin.Key) error {
103+
sigHash, err := SignatureHashForMessage(r.SenderHandle + r.AssetID +
104+
strconv.FormatUint(r.Amount, 10) + r.DateTime + r.Purpose)
105+
if err != nil {
106+
return errors.Wrap(err, "signature hash")
107+
}
108+
109+
sig, err := key.Sign(sigHash.Bytes())
110+
if err != nil {
111+
return errors.Wrap(err, "sign")
112+
}
113+
114+
r.Signature = sig.ToCompact()
115+
return nil
116+
}
117+
118+
func (r PaymentRequestRequest) CheckSignature(publicKey bitcoin.PublicKey) error {
119+
sigHash, err := SignatureHashForMessage(r.SenderHandle + r.AssetID +
120+
strconv.FormatUint(r.Amount, 10) + r.DateTime + r.Purpose)
121+
if err != nil {
122+
return errors.Wrap(err, "signature hash")
123+
}
124+
125+
sig, err := bitcoin.SignatureFromCompact(r.Signature)
126+
if err != nil {
127+
return errors.Wrap(err, fmt.Sprintf("parse signature: %s", r.Signature))
128+
}
129+
130+
if !sig.Verify(sigHash.Bytes(), publicKey) {
131+
return ErrInvalidSignature
132+
}
133+
134+
return nil
135+
}
136+
137+
// PaymentRequestResponse is the raw response from a PaymentRequest endpoint.
138+
type PaymentRequestResponse struct {
139+
PaymentRequest string `json:"paymentRequest"`
140+
Outputs []string `json:"outputs"`
141+
}
142+
143+
// PaymentRequest is the processed response from a PaymentRequest endpoint.
144+
type PaymentRequest struct {
145+
Tx wire.MsgTx
146+
Outputs []wire.TxOut
147+
}

bsvalias/payment.go

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,7 @@ func (i *Identity) GetPaymentDestination(senderName, senderHandle, purpose strin
2424
return nil, errors.Wrap(err, "payment request")
2525
}
2626

27-
request := struct {
28-
SenderName string `json:"senderName"`
29-
SenderHandle string `json:"senderHandle"`
30-
DateTime string `json:"dt"`
31-
Amount uint64 `json:"amount"`
32-
Purpose string `json:"purpose"`
33-
Signature string `json:"signature"`
34-
}{
27+
request := PaymentDestinationRequest{
3528
SenderName: senderName,
3629
SenderHandle: senderHandle,
3730
DateTime: time.Now().UTC().Format("2006-01-02T15:04:05.999Z"),
@@ -54,12 +47,10 @@ func (i *Identity) GetPaymentDestination(senderName, senderHandle, purpose strin
5447
request.Signature = sig.ToCompact()
5548
}
5649

57-
var response struct {
58-
Output string `json:"output"`
59-
}
60-
6150
url = strings.ReplaceAll(url, "{alias}", i.Alias)
6251
url = strings.ReplaceAll(url, "{domain.tld}", i.Hostname)
52+
53+
var response PaymentDestinationResponse
6354
if err := post(url, request, &response); err != nil {
6455
return nil, errors.Wrap(err, "http get")
6556
}
@@ -76,11 +67,6 @@ func (i *Identity) GetPaymentDestination(senderName, senderHandle, purpose strin
7667
return result, nil
7768
}
7869

79-
type PaymentRequest struct {
80-
Tx wire.MsgTx
81-
Outputs []wire.TxOut
82-
}
83-
8470
// GetPaymentRequest gets a payment request from the identity.
8571
// senderHandle is required.
8672
// assetID can be empty or "BSV" to request bitcoin.
@@ -94,15 +80,7 @@ func (i *Identity) GetPaymentRequest(senderName, senderHandle, purpose, assetID
9480
return PaymentRequest{}, errors.Wrap(err, "payment request")
9581
}
9682

97-
request := struct {
98-
SenderName string `json:"senderName"`
99-
SenderHandle string `json:"senderHandle"`
100-
DateTime string `json:"dt"`
101-
AssetID string `json:"assetID"`
102-
Amount uint64 `json:"amount"`
103-
Purpose string `json:"purpose"`
104-
Signature string `json:"signature"`
105-
}{
83+
request := PaymentRequestRequest{
10684
SenderName: senderName,
10785
SenderHandle: senderHandle,
10886
DateTime: time.Now().UTC().Format("2006-01-02T15:04:05.999Z"),
@@ -126,13 +104,10 @@ func (i *Identity) GetPaymentRequest(senderName, senderHandle, purpose, assetID
126104
request.Signature = sig.ToCompact()
127105
}
128106

129-
var response struct {
130-
PaymentRequest string `json:"paymentRequest"`
131-
Outputs []string `json:"outputs"`
132-
}
133-
134107
url = strings.ReplaceAll(url, "{alias}", i.Alias)
135108
url = strings.ReplaceAll(url, "{domain.tld}", i.Hostname)
109+
110+
var response PaymentRequestResponse
136111
if err := post(url, request, &response); err != nil {
137112
return PaymentRequest{}, errors.Wrap(err, "http get")
138113
}

bsvalias/site.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ const (
1414
URLNamePaymentRequest = "f7ecaab847eb"
1515
)
1616

17-
type Capabilities struct {
18-
Version string `json:"bsvalias"`
19-
Capabilities map[string]interface{} `json:"capabilities"`
20-
}
21-
22-
type Site struct {
23-
Capabilities Capabilities
24-
URL string `json:"url"`
25-
}
26-
2717
func GetSite(ctx context.Context, domain string) (Site, error) {
2818
// Lookup SRV record for possible hosting other than specified domain
2919
_, records, _ := net.LookupSRV("bsvalias", "tcp", domain)

json/decode_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -640,9 +640,9 @@ var unmarshalTests = []unmarshalTest{
640640
out: S5{S8: S8{S9: S9{Y: 2}}},
641641
},
642642
{
643-
in: `{"X": 1,"Y":2}`,
644-
ptr: new(S5),
645-
err: fmt.Errorf("json: unknown field \"X\""),
643+
in: `{"X": 1,"Y":2}`,
644+
ptr: new(S5),
645+
err: fmt.Errorf("json: unknown field \"X\""),
646646
disallowUnknownFields: true,
647647
},
648648
{
@@ -651,9 +651,9 @@ var unmarshalTests = []unmarshalTest{
651651
out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}},
652652
},
653653
{
654-
in: `{"X": 1,"Y":2}`,
655-
ptr: new(S10),
656-
err: fmt.Errorf("json: unknown field \"X\""),
654+
in: `{"X": 1,"Y":2}`,
655+
ptr: new(S10),
656+
err: fmt.Errorf("json: unknown field \"X\""),
657657
disallowUnknownFields: true,
658658
},
659659

@@ -861,8 +861,8 @@ var unmarshalTests = []unmarshalTest{
861861
"Q": 18,
862862
"extra": true
863863
}`,
864-
ptr: new(Top),
865-
err: fmt.Errorf("json: unknown field \"extra\""),
864+
ptr: new(Top),
865+
err: fmt.Errorf("json: unknown field \"extra\""),
866866
disallowUnknownFields: true,
867867
},
868868
{
@@ -888,8 +888,8 @@ var unmarshalTests = []unmarshalTest{
888888
"Z": 17,
889889
"Q": 18
890890
}`,
891-
ptr: new(Top),
892-
err: fmt.Errorf("json: unknown field \"extra\""),
891+
ptr: new(Top),
892+
err: fmt.Errorf("json: unknown field \"extra\""),
893893
disallowUnknownFields: true,
894894
},
895895
// issue 26444

0 commit comments

Comments
 (0)