Skip to content
Open
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
4 changes: 2 additions & 2 deletions psbt/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/btcsuite/btcd/psbt/v2

go 1.25
go 1.25.0

require (
github.com/btcsuite/btcd/address/v2 v2.0.0
Expand All @@ -10,14 +10,14 @@ require (
github.com/btcsuite/btcd/txscript/v2 v2.0.0
github.com/btcsuite/btcd/wire/v2 v2.0.0
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/stretchr/testify v1.10.0
)

require (
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect
github.com/btcsuite/btclog v1.0.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
Expand Down
64 changes: 64 additions & 0 deletions psbt/partial_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type PInput struct {
TaprootBip32Derivation []*TaprootBip32Derivation
TaprootInternalKey []byte
TaprootMerkleRoot []byte
SilentPaymentShares []SilentPaymentShare
SilentPaymentDLEQs []SilentPaymentDLEQ
Unknowns []*Unknown
}

Expand Down Expand Up @@ -363,6 +365,40 @@ func (pi *PInput) deserialize(r io.Reader) error {

pi.TaprootMerkleRoot = value

case SilentPaymentShareInputType:
share, err := ReadSilentPaymentShare(keyData, value)
if err != nil {
return err
}

// Duplicate keys are not allowed.
for _, x := range pi.SilentPaymentShares {
if x.EqualKey(share) {
return ErrDuplicateKey
}
}

pi.SilentPaymentShares = append(
pi.SilentPaymentShares, *share,
)

case SilentPaymentDLEQInputType:
proof, err := ReadSilentPaymentDLEQ(keyData, value)
if err != nil {
return err
}

// Duplicate keys are not allowed.
for _, x := range pi.SilentPaymentDLEQs {
if x.EqualKey(proof) {
return ErrDuplicateKey
}
}

pi.SilentPaymentDLEQs = append(
pi.SilentPaymentDLEQs, *proof,
)

default:
// A fall through case for any proprietary types.
keyCodeAndData := append(
Expand Down Expand Up @@ -572,6 +608,34 @@ func (pi *PInput) serialize(w io.Writer) error {
return err
}
}

// Serialize the input's silent payment shares.
for _, share := range pi.SilentPaymentShares {
keyBytes, valueBytes := SerializeSilentPaymentShare(
&share,
)
err := serializeKVPairWithType(
w, uint8(SilentPaymentShareInputType), keyBytes,
valueBytes,
)
if err != nil {
return err
}
}

// Serialize the input's silent payment DLEQ proofs.
for _, dleq := range pi.SilentPaymentDLEQs {
keyBytes, valueBytes := SerializeSilentPaymentDLEQ(
&dleq,
)
err := serializeKVPairWithType(
w, uint8(SilentPaymentDLEQInputType), keyBytes,
valueBytes,
)
if err != nil {
return err
}
}
}

if pi.FinalScriptSig != nil {
Expand Down
52 changes: 52 additions & 0 deletions psbt/partial_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package psbt

import (
"bytes"
"encoding/binary"
"io"
"sort"

Expand All @@ -17,6 +18,8 @@ type POutput struct {
TaprootInternalKey []byte
TaprootTapTree []byte
TaprootBip32Derivation []*TaprootBip32Derivation
SilentPaymentInfo *SilentPaymentInfo
SilentPaymentLabel *uint32
Unknowns []*Unknown
}

Expand Down Expand Up @@ -144,6 +147,31 @@ func (po *POutput) deserialize(r io.Reader) error {
po.TaprootBip32Derivation, taprootDerivation,
)

case SilentPaymentV0InfoOutputType:
if po.SilentPaymentInfo != nil {
return ErrDuplicateKey
}

info, err := ReadSilentPaymentInfo(value)
if err != nil {
return err
}

po.SilentPaymentInfo = info

case SilentPaymentV0LabelOutputType:
if po.SilentPaymentLabel != nil {
return ErrDuplicateKey
}

if len(value) != uint32Size {
return ErrInvalidKeyData
}

label := binary.LittleEndian.Uint32(value)

po.SilentPaymentLabel = &label

default:
// A fall through case for any proprietary types.
keyCodeAndData := append(
Expand Down Expand Up @@ -246,6 +274,30 @@ func (po *POutput) serialize(w io.Writer) error {
}
}

if po.SilentPaymentInfo != nil {
err := serializeKVPairWithType(
w, uint8(SilentPaymentV0InfoOutputType), nil,
SerializeSilentPaymentInfo(po.SilentPaymentInfo),
)
if err != nil {
return err
}
}

if po.SilentPaymentLabel != nil {
var labelBytes [uint32Size]byte
binary.LittleEndian.PutUint32(
labelBytes[:], *po.SilentPaymentLabel,
)
err := serializeKVPairWithType(
w, uint8(SilentPaymentV0LabelOutputType), nil,
labelBytes[:],
)
if err != nil {
return err
}
}

// Unknown is a special case; we don't have a key type, only a key and
// a value field
for _, kv := range po.Unknowns {
Expand Down
88 changes: 78 additions & 10 deletions psbt/psbt.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ type Packet struct {
// derived.
XPubs []XPub

// SilentPaymentShares is a list of ECDH shares that are used to derive
// the shared secret for a silent payment.
SilentPaymentShares []SilentPaymentShare

// SilentPaymentDLEQs is a list of DLEQ proofs that are used to prove
// the validity of the shares for a silent payment.
SilentPaymentDLEQs []SilentPaymentDLEQ

// Unknowns are the set of custom types (global only) within this PSBT.
Unknowns []*Unknown
}
Expand Down Expand Up @@ -169,14 +177,18 @@ func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) {
inSlice := make([]PInput, len(tx.TxIn))
outSlice := make([]POutput, len(tx.TxOut))
xPubSlice := make([]XPub, 0)
spsSlice := make([]SilentPaymentShare, 0)
spdSlice := make([]SilentPaymentDLEQ, 0)
unknownSlice := make([]*Unknown, 0)

return &Packet{
UnsignedTx: tx,
Inputs: inSlice,
Outputs: outSlice,
XPubs: xPubSlice,
Unknowns: unknownSlice,
UnsignedTx: tx,
Inputs: inSlice,
Outputs: outSlice,
XPubs: xPubSlice,
SilentPaymentShares: spsSlice,
SilentPaymentDLEQs: spdSlice,
Unknowns: unknownSlice,
}, nil
}

Expand Down Expand Up @@ -241,6 +253,8 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {
// break at the separator.
var (
xPubSlice []XPub
spsSlice []SilentPaymentShare
spdSlice []SilentPaymentDLEQ
unknownSlice []*Unknown
)
for {
Expand Down Expand Up @@ -275,6 +289,36 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {

xPubSlice = append(xPubSlice, *xPub)

case SilentPaymentShareType:
share, err := ReadSilentPaymentShare(keydata, value)
if err != nil {
return nil, err
}

// Duplicate keys are not allowed.
for _, x := range spsSlice {
if x.EqualKey(share) {
return nil, ErrDuplicateKey
}
}

spsSlice = append(spsSlice, *share)

case SilentPaymentDLEQType:
proof, err := ReadSilentPaymentDLEQ(keydata, value)
if err != nil {
return nil, err
}

// Duplicate keys are not allowed.
for _, x := range spdSlice {
if x.EqualKey(proof) {
return nil, ErrDuplicateKey
}
}

spdSlice = append(spdSlice, *proof)

default:
keyintanddata := []byte{byte(keyint)}
keyintanddata = append(keyintanddata, keydata...)
Expand Down Expand Up @@ -313,11 +357,13 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {

// Populate the new Packet object.
newPsbt := Packet{
UnsignedTx: msgTx,
Inputs: inSlice,
Outputs: outSlice,
XPubs: xPubSlice,
Unknowns: unknownSlice,
UnsignedTx: msgTx,
Inputs: inSlice,
Outputs: outSlice,
XPubs: xPubSlice,
SilentPaymentShares: spsSlice,
SilentPaymentDLEQs: spdSlice,
Unknowns: unknownSlice,
}

// Extended sanity checking is applied here to make sure the
Expand Down Expand Up @@ -369,6 +415,28 @@ func (p *Packet) Serialize(w io.Writer) error {
}
}

// Serialize the global silent payment shares.
for _, share := range p.SilentPaymentShares {
keyBytes, valueBytes := SerializeSilentPaymentShare(&share)
err := serializeKVPairWithType(
w, uint8(SilentPaymentShareType), keyBytes, valueBytes,
)
if err != nil {
return err
}
}

// Serialize the global silent payment DLEQ proofs.
for _, dleq := range p.SilentPaymentDLEQs {
keyBytes, valueBytes := SerializeSilentPaymentDLEQ(&dleq)
err := serializeKVPairWithType(
w, uint8(SilentPaymentDLEQType), keyBytes, valueBytes,
)
if err != nil {
return err
}
}

// Unknown is a special case; we don't have a key type, only a key and
// a value field
for _, kv := range p.Unknowns {
Expand Down
Loading
Loading