From ce1d46191898d106b1a0859c072a50dbc5b2f95f Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 28 Dec 2024 17:14:10 +0100 Subject: [PATCH] psbt: add global silent payment DLEQ proofs --- btcutil/psbt/psbt.go | 46 ++++++++++++++++++--- btcutil/psbt/silentpayments.go | 75 ++++++++++++++++++++++++++++++++++ btcutil/psbt/types.go | 4 ++ 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/btcutil/psbt/psbt.go b/btcutil/psbt/psbt.go index 5db983b74f..3f11e4c3ae 100644 --- a/btcutil/psbt/psbt.go +++ b/btcutil/psbt/psbt.go @@ -139,6 +139,10 @@ type Packet struct { // 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 } @@ -165,14 +169,16 @@ func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) { inSlice := make([]PInput, len(tx.TxIn)) outSlice := make([]POutput, len(tx.TxOut)) - spSlice := make([]SilentPaymentShare, 0) + spsSlice := make([]SilentPaymentShare, 0) + spdSlice := make([]SilentPaymentDLEQ, 0) unknownSlice := make([]*Unknown, 0) return &Packet{ UnsignedTx: tx, Inputs: inSlice, Outputs: outSlice, - SilentPaymentShares: spSlice, + SilentPaymentShares: spsSlice, + SilentPaymentDLEQs: spdSlice, Unknowns: unknownSlice, }, nil } @@ -237,7 +243,8 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { // Next we parse any unknowns that may be present, making sure that we // break at the separator. var ( - spSlice []SilentPaymentShare + spsSlice []SilentPaymentShare + spdSlice []SilentPaymentDLEQ unknownSlice []*Unknown ) for { @@ -264,13 +271,28 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { } // Duplicate keys are not allowed. - for _, x := range spSlice { + for _, x := range spsSlice { if x.EqualKey(share) { return nil, ErrDuplicateKey } } - spSlice = append(spSlice, *share) + 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)} @@ -313,7 +335,8 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { UnsignedTx: msgTx, Inputs: inSlice, Outputs: outSlice, - SilentPaymentShares: spSlice, + SilentPaymentShares: spsSlice, + SilentPaymentDLEQs: spdSlice, Unknowns: unknownSlice, } @@ -364,6 +387,17 @@ func (p *Packet) Serialize(w io.Writer) error { } } + // 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 { diff --git a/btcutil/psbt/silentpayments.go b/btcutil/psbt/silentpayments.go index b593b41ce0..d62d85360e 100644 --- a/btcutil/psbt/silentpayments.go +++ b/btcutil/psbt/silentpayments.go @@ -125,3 +125,78 @@ func SerializeSilentPaymentShare(share *SilentPaymentShare) ([]byte, []byte) { return keyData, share.Share } + +// SilentPaymentDLEQ is a DLEQ proof for a silent payment share. +type SilentPaymentDLEQ struct { + // ScanKey is the silent payment recipient's scan key. + ScanKey []byte + + // OutPoints is the list of outpoints the share proof is for. + OutPoints []wire.OutPoint + + // Proof is the DLEQ proof for the share with the same key. + Proof []byte +} + +// EqualKey returns true if this silent payment DLEQ's key data is the same as +// the given silent payment DLEQ. +func (d *SilentPaymentDLEQ) EqualKey(other *SilentPaymentDLEQ) bool { + if !bytes.Equal(d.ScanKey, other.ScanKey) { + return false + } + + return equalOutPoints(d.OutPoints, other.OutPoints) +} + +// ReadSilentPaymentDLEQ deserializes a silent payment DLEQ proof from the given +// key data and value. +func ReadSilentPaymentDLEQ(keyData, value []byte) (*SilentPaymentDLEQ, error) { + // There must be at least the scan key in the key data. + if len(keyData) < secp.PubKeyBytesLenCompressed { + return nil, ErrInvalidKeyData + } + + // The remaining bytes of the key data are outpoints. + outPointsLen := len(keyData) - secp.PubKeyBytesLenCompressed + if outPointsLen%outPointSize != 0 { + return nil, ErrInvalidKeyData + } + + // The proof must be 64 bytes. + if len(value) != 64 { + return nil, ErrInvalidPsbtFormat + } + + share := &SilentPaymentDLEQ{ + ScanKey: keyData[:secp.PubKeyBytesLenCompressed], + OutPoints: make([]wire.OutPoint, outPointsLen/outPointSize), + Proof: value, + } + + for i := 0; i < outPointsLen; i += outPointSize { + idx := i / outPointSize + copy(share.OutPoints[idx].Hash[:], keyData[i:i+sha256.Size]) + share.OutPoints[idx].Index = binary.LittleEndian.Uint32( + keyData[i+sha256.Size : i+sha256.Size+uint32Size], + ) + } + + return share, nil +} + +// SerializeSilentPaymentDLEQ serializes a silent payment DLEQ proof to key data +// and value. +func SerializeSilentPaymentDLEQ(dleq *SilentPaymentDLEQ) ([]byte, []byte) { + keyData := make( + []byte, 0, len(dleq.ScanKey)+len(dleq.OutPoints)*outPointSize, + ) + keyData = append(keyData, dleq.ScanKey...) + for _, outPoint := range dleq.OutPoints { + keyData = append(keyData, outPoint.Hash[:]...) + var idx [4]byte + binary.LittleEndian.PutUint32(idx[:], outPoint.Index) + keyData = append(keyData, idx[:]...) + } + + return keyData, dleq.Proof +} diff --git a/btcutil/psbt/types.go b/btcutil/psbt/types.go index 723b8be317..817eb67835 100644 --- a/btcutil/psbt/types.go +++ b/btcutil/psbt/types.go @@ -33,6 +33,10 @@ const ( // SilentPaymentShareType is used to house the ECDH shares for silent // payments. SilentPaymentShareType GlobalType = 0x07 + + // SilentPaymentDLEQType is used to house the DLEQ proofs for silent + // payments. + SilentPaymentDLEQType GlobalType = 0x08 // VersionType houses the global version number of this PSBT. There is // no key (only contains the byte type), then the value if omitted, is