Skip to content

Commit

Permalink
psbt: add global silent payment DLEQ proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Dec 28, 2024
1 parent 7712fa6 commit ce1d461
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 6 deletions.
46 changes: 40 additions & 6 deletions btcutil/psbt/psbt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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)}
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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 {
Expand Down
75 changes: 75 additions & 0 deletions btcutil/psbt/silentpayments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions btcutil/psbt/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ce1d461

Please sign in to comment.