From a874840c891dd2c88a6493e96910ca122a30d231 Mon Sep 17 00:00:00 2001 From: octobocto Date: Mon, 5 Oct 2026 12:31:31 -0700 Subject: [PATCH 1/2] orchestrator: fix verify_signature param order --- .../sidechain/bitassets/handler.go | 3 ++- .../bitassets/transfer_bitasset_test.go | 16 ++++++++++++++++ .../sidechain/truthcoin/handler.go | 2 +- .../sidechain/truthcoin/handler_test.go | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/sidechain-orchestrator/sidechain/bitassets/handler.go b/sidechain-orchestrator/sidechain/bitassets/handler.go index cc1eb2d3b..6abe1c5c7 100644 --- a/sidechain-orchestrator/sidechain/bitassets/handler.go +++ b/sidechain-orchestrator/sidechain/bitassets/handler.go @@ -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 } diff --git a/sidechain-orchestrator/sidechain/bitassets/transfer_bitasset_test.go b/sidechain-orchestrator/sidechain/bitassets/transfer_bitasset_test.go index c3e5de3ec..28fb5b5c4 100644 --- a/sidechain-orchestrator/sidechain/bitassets/transfer_bitasset_test.go +++ b/sidechain-orchestrator/sidechain/bitassets/transfer_bitasset_test.go @@ -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) +} diff --git a/sidechain-orchestrator/sidechain/truthcoin/handler.go b/sidechain-orchestrator/sidechain/truthcoin/handler.go index af78212cd..36ec20ec0 100644 --- a/sidechain-orchestrator/sidechain/truthcoin/handler.go +++ b/sidechain-orchestrator/sidechain/truthcoin/handler.go @@ -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 } diff --git a/sidechain-orchestrator/sidechain/truthcoin/handler_test.go b/sidechain-orchestrator/sidechain/truthcoin/handler_test.go index 0ab5f2177..afff7d533 100644 --- a/sidechain-orchestrator/sidechain/truthcoin/handler_test.go +++ b/sidechain-orchestrator/sidechain/truthcoin/handler_test.go @@ -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])) +} From a4663e63d47e94a16198f6eb63ecfe0b0b3f8263 Mon Sep 17 00:00:00 2001 From: octobocto Date: Mon, 5 Oct 2026 12:31:32 -0700 Subject: [PATCH 2/2] orchestrator: read deposit inputs as txid:vout --- sidechain-orchestrator/api/explorer_core.go | 27 +++++++++++++--- sidechain-orchestrator/api/explorer_test.go | 34 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/sidechain-orchestrator/api/explorer_core.go b/sidechain-orchestrator/api/explorer_core.go index c13dfc860..903d9942c 100644 --- a/sidechain-orchestrator/api/explorer_core.go +++ b/sidechain-orchestrator/api/explorer_core.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "math" + "strconv" + "strings" "connectrpc.com/connect" @@ -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"` @@ -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: @@ -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) { diff --git a/sidechain-orchestrator/api/explorer_test.go b/sidechain-orchestrator/api/explorer_test.go index b9eaa913d..41d61a5b8 100644 --- a/sidechain-orchestrator/api/explorer_test.go +++ b/sidechain-orchestrator/api/explorer_test.go @@ -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) {