Skip to content
Merged
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
27 changes: 22 additions & 5 deletions sidechain-orchestrator/api/explorer_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"

"connectrpc.com/connect"

Expand Down Expand Up @@ -139,10 +141,7 @@ type nodeTx struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
} `json:"Regular"`
Deposit *struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
} `json:"Deposit"`
Deposit *string `json:"Deposit"`
// An older node names a coinbase by the block's merkle root.
Coinbase *struct {
Txid string `json:"txid"`
Expand Down Expand Up @@ -205,8 +204,13 @@ func nodeTransaction(ctx context.Context, src source, txid string) (*pb.Transact
for _, in := range tx.Inputs {
switch {
case in.Deposit != nil:
mainTxid, vout, err := parseMainchainOutpoint(*in.Deposit)
if err != nil {
return nil, connect.NewError(connect.CodeInternal,
fmt.Errorf("read transaction %s: %w", txid, err))
}
out.Inputs = append(out.Inputs, &pb.Coin{
Txid: in.Deposit.Txid, Vout: in.Deposit.Vout, OutpointKind: "deposit",
Txid: mainTxid, Vout: vout, OutpointKind: "deposit",
})
out.Kind = pb.Kind_KIND_DEPOSIT
case in.Regular != nil:
Expand Down Expand Up @@ -239,6 +243,19 @@ func nodeTransaction(ctx context.Context, src source, txid string) (*pb.Transact
return out, nil
}

// parseMainchainOutpoint reads a "txid:vout" outpoint.
func parseMainchainOutpoint(outpoint string) (string, uint32, error) {
txid, vout, ok := strings.Cut(outpoint, ":")
if !ok || txid == "" {
return "", 0, fmt.Errorf("deposit outpoint %q is not txid:vout", outpoint)
}
n, err := strconv.ParseUint(vout, 10, 32)
if err != nil {
return "", 0, fmt.Errorf("deposit outpoint %q: %w", outpoint, err)
}
return txid, uint32(n), nil
}

// nodeTransactionInfo reads the confirmation and the fee a bare transaction
// does not carry. A node without the method answers false.
func nodeTransactionInfo(ctx context.Context, src source, txid string) (nodeTxInfo, bool) {
Expand Down
34 changes: 34 additions & 0 deletions sidechain-orchestrator/api/explorer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,40 @@ func TestNodeTransactionNamesACoinbaseInputByTxid(t *testing.T) {
}
}

// A node names a deposit input as a "txid:vout" string. The body is a real
// truthcoin 0.19 transaction from alphanet block 207.
func TestNodeTransactionReadsADepositInput(t *testing.T) {
node := &recordingNode{answers: map[string]string{
"get_transaction": `{"inputs":[{"Deposit":"ba4ce57f4c5fff32deac1b7e8ad2a4325e8f012061d741793d41016a178ccb2e:0"}],` +
`"outputs":[],"memo":"","data":{"Trade":{"market_id":[49,54,120,67,13,152],"outcome_index":1,` +
`"shares":28500,"trader":"3VydCT3fpNEheSB4k8M1u8fNUVcu","limit_sats":23442,"tx_pow_nonce":null,` +
`"prev_block_hash":"a42693b969adbb608294f9195206097eacc117b1038586fe078290ca553b763b"}}}`,
"get_transaction_info": `null`,
}}
src := source{name: "truthcoin", node: node}

tx, err := nodeTransaction(context.Background(), src, "abc")
if err != nil {
t.Fatalf("read the transaction: %v", err)
}
if got := len(tx.GetInputs()); got != 1 {
t.Fatalf("the transaction holds %d inputs, want 1", got)
}
in := tx.GetInputs()[0]
if got := in.GetOutpointKind(); got != "deposit" {
t.Errorf("the input reads as %q, want deposit", got)
}
if got := in.GetTxid(); got != "ba4ce57f4c5fff32deac1b7e8ad2a4325e8f012061d741793d41016a178ccb2e" {
t.Errorf("the input names %q", got)
}
if got := in.GetVout(); got != 0 {
t.Errorf("the input names output %d, want 0", got)
}
if got := tx.GetKind(); got != pb.Kind_KIND_DEPOSIT {
t.Errorf("the transaction reads as %v, want a deposit", got)
}
}

// A wrapped transaction names its block. The view reads a height, so the
// block hash must resolve to one.
func TestWrappedTransactionResolvesItsHeight(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion sidechain-orchestrator/sidechain/bitassets/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ func (h *Handler) SignArbitraryMsgAsAddr(ctx context.Context, req *connect.Reque

func (h *Handler) VerifySignature(ctx context.Context, req *connect.Request[pb.VerifySignatureRequest]) (*connect.Response[pb.VerifySignatureResponse], error) {
var valid bool
params := []any{req.Msg.Msg, req.Msg.Signature, req.Msg.VerifyingKey}
// The sign_arbitrary_msg methods sign with the arbitrary dst.
params := []any{req.Msg.Signature, req.Msg.VerifyingKey, "arbitrary", req.Msg.Msg}
if err := h.proxy.Client.Call(ctx, "verify_signature", params, &valid); err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,19 @@ func TestTransferBitAssetSendsDestBeforeAssetID(t *testing.T) {
nil,
}, *params)
}

// The node signature is verify_signature(signature, verifying_key, dst, msg).
func TestVerifySignatureSendsSignatureFirst(t *testing.T) {
proxy, params := paramsNode(t, `true`)

resp, err := NewHandler(proxy).VerifySignature(context.Background(),
connect.NewRequest(&pb.VerifySignatureRequest{
Msg: "the message",
Signature: "0xsig",
VerifyingKey: "ba-svk1key",
}))
require.NoError(t, err)
assert.True(t, resp.Msg.Valid)

assert.Equal(t, []any{"0xsig", "ba-svk1key", "arbitrary", "the message"}, *params)
}
2 changes: 1 addition & 1 deletion sidechain-orchestrator/sidechain/truthcoin/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ func (h *Handler) SignArbitraryMsgAsAddr(ctx context.Context, req *connect.Reque

func (h *Handler) VerifySignature(ctx context.Context, req *connect.Request[pb.VerifySignatureRequest]) (*connect.Response[pb.VerifySignatureResponse], error) {
var valid bool
params := []any{req.Msg.Msg, req.Msg.Signature, req.Msg.VerifyingKey, req.Msg.Dst}
params := []any{req.Msg.Signature, req.Msg.VerifyingKey, req.Msg.Dst, req.Msg.Msg}
if err := h.proxy.Client.Call(ctx, "verify_signature", params, &valid); err != nil {
return nil, err
}
Expand Down
17 changes: 17 additions & 0 deletions sidechain-orchestrator/sidechain/truthcoin/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,20 @@ func TestMarketSellSendsRequestObject(t *testing.T) {
"min_proceeds": 50
}]`, string((*params)[0]))
}

// The node signature is verify_signature(signature, verifying_key, dst, msg).
func TestVerifySignatureSendsSignatureFirst(t *testing.T) {
h, params := capturingRPC(t, `true`)

resp, err := h.VerifySignature(context.Background(), connect.NewRequest(&pb.VerifySignatureRequest{
Msg: "the message",
Signature: "0xsig",
VerifyingKey: "ba-svk1key",
Dst: "arbitrary",
}))
require.NoError(t, err)
assert.True(t, resp.Msg.Valid)

require.Len(t, *params, 1)
assert.JSONEq(t, `["0xsig","ba-svk1key","arbitrary","the message"]`, string((*params)[0]))
}
Loading