From 52add2f6a3dc1e490751fc700b99ba59df9bc135 Mon Sep 17 00:00:00 2001 From: Dusan Sekulic Date: Tue, 9 Jun 2020 15:35:17 +0200 Subject: [PATCH] Payment: support for confidential addresses (#86) * init * Confidential p2sh/p2pkh addresses added * Confidential p2sh/p2pkh addresses added * ConfidentialWitnessPubKeyHash added * confidential payment example added --- address/address.go | 21 ++++-- address/address_test.go | 26 ++++--- example.go | 2 +- payment/examples_test.go | 24 ++++++- payment/payment.go | 150 ++++++++++++++++++++++++++++++++++++--- payment/payment_test.go | 134 ++++++++++++++++++++++++++++++++-- pset/blinder_test.go | 2 +- pset/pset_test.go | 2 +- 8 files changed, 324 insertions(+), 37 deletions(-) diff --git a/address/address.go b/address/address.go index c60c774..f7a4bea 100644 --- a/address/address.go +++ b/address/address.go @@ -33,7 +33,7 @@ type Blech32 struct { Prefix string Version byte PublicKey []byte - Data []byte + Program []byte } // FromBase58 decodes a string that was base58 encoded and verifies the checksum. @@ -184,14 +184,24 @@ func FromBlech32(address string) (*Blech32, error) { "version 0: %v", len(regrouped)) } - return &Blech32{prefix, version, nil, regrouped}, nil + return &Blech32{ + prefix, + version, + regrouped[:33], + regrouped[33:], + }, nil } // ToBlech32 encodes a byte slice into a blech32 string func ToBlech32(bl *Blech32) (string, error) { // Group the address bytes into 5 bit groups, as this is what is used to // encode each character in the address string. - converted, err := blech32.ConvertBits(bl.Data, 8, 5, true) + converted, err := blech32.ConvertBits( + append(bl.PublicKey, bl.Program...), + 8, + 5, + true, + ) if err != nil { return "", err } @@ -212,7 +222,10 @@ func ToBlech32(bl *Blech32) (string, error) { return "", fmt.Errorf("invalid blech32 address: %v", err) } - if blech.Version != bl.Version || !bytes.Equal(blech.Data, bl.Data) { + blechData := append(blech.PublicKey, blech.Program...) + blData := append(bl.PublicKey, bl.Program...) + + if blech.Version != bl.Version || !bytes.Equal(blechData, blData) { return "", fmt.Errorf("invalid segwit address") } diff --git a/address/address_test.go b/address/address_test.go index 1f9fc84..8f54527 100644 --- a/address/address_test.go +++ b/address/address_test.go @@ -73,12 +73,11 @@ func TestToBlech32_P2WPKH(t *testing.T) { t.Error(err) } - program1 := append(pkBytes, witProg1Bytes...) - blech32Addr := &address.Blech32{ - Prefix: "el", - Version: 0, - Data: program1, + Prefix: "el", + Version: 0, + PublicKey: pkBytes, + Program: witProg1Bytes, } blech32, err := address.ToBlech32(blech32Addr) @@ -102,12 +101,11 @@ func TestToBlech32_P2WSH(t *testing.T) { t.Error(err) } - program2 := append(pkBytes, witProg2Bytes...) - blech32Addr := &address.Blech32{ - Prefix: "el", - Version: 0, - Data: program2, + Prefix: "el", + Version: 0, + PublicKey: pkBytes, + Program: witProg2Bytes, } blech32, err := address.ToBlech32(blech32Addr) @@ -129,12 +127,12 @@ func TestFromBlech32_P2WPKH(t *testing.T) { t.Error("TestFromBlech32_P2WPKH: wrong version") } - resPubKey := blech32.Data[:33] + resPubKey := blech32.PublicKey if hex.EncodeToString(resPubKey) != pubKey { t.Error("TestFromBlech32_P2WPKH: wrong pub key") } - resProgram := blech32.Data[33:] + resProgram := blech32.Program if hex.EncodeToString(resProgram) != witProg1 { t.Error("TestFromBlech32_P2WPKH: wrong witness program") } @@ -149,12 +147,12 @@ func TestFromBlech32_P2WSH(t *testing.T) { t.Error("TestFromBlech32_P2WSH: wrong version") } - resPubKey := blech32.Data[:33] + resPubKey := blech32.PublicKey if hex.EncodeToString(resPubKey) != pubKey { t.Error("TestFromBlech32_P2WSH: wrong pub key") } - resProgram := blech32.Data[33:] + resProgram := blech32.Program if hex.EncodeToString(resProgram) != witProg2 { t.Error("TestFromBlech32_P2WSH: wrong witness program") } diff --git a/example.go b/example.go index c8d8cb6..210fd5c 100644 --- a/example.go +++ b/example.go @@ -22,7 +22,7 @@ func main() { privateKeyBytes, _ := hex.DecodeString(privKeyHex) _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) - pay := payment.FromPublicKey(publicKey, &network.Regtest) + pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) legacyAddress := pay.PubKeyHash() segwitAddress, _ := pay.WitnessPubKeyHash() println(legacyAddress) diff --git a/payment/examples_test.go b/payment/examples_test.go index b4b4688..9e0a07b 100644 --- a/payment/examples_test.go +++ b/payment/examples_test.go @@ -17,14 +17,14 @@ var privateKeyBytes, _ = hex.DecodeString(privateKeyHex) //This examples shows how standard P2PKH address can be created func ExampleFromPublicKey() { _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) - pay := payment.FromPublicKey(publicKey, &network.Regtest) + pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) fmt.Printf("P2PKH address %v\n:", pay.PubKeyHash()) } //This examples shows how nested payment can be done in order to create non native SegWit(P2SH-P2WPKH) address func ExampleFromPayment() { _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) - p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest) + p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest, nil) pay, err := payment.FromPayment(p2wpkh) p2sh, err := pay.ScriptHash() if err != nil { @@ -32,3 +32,23 @@ func ExampleFromPayment() { } fmt.Printf("Non native SegWit address %v\n:", p2sh) } + +func ExampleConfidentialWitnessPubKeyHash() { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + fmt.Println(err) + } + blindingKey := pk.PubKey() + + privkey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + fmt.Println(err) + } + + p2wpkh := payment.FromPublicKey(privkey.PubKey(), &network.Regtest, blindingKey) + confidentialWpkh, err := p2wpkh.ConfidentialWitnessPubKeyHash() + if err != nil { + fmt.Println(err) + } + fmt.Printf("Confidential SegWit address %v\n:", confidentialWpkh) +} diff --git a/payment/payment.go b/payment/payment.go index 2707050..9c3858d 100644 --- a/payment/payment.go +++ b/payment/payment.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil/base58" "github.com/vulpemventures/go-elements/address" "github.com/vulpemventures/go-elements/network" "golang.org/x/crypto/ripemd160" @@ -28,7 +29,11 @@ type Payment struct { // pub 036f5646ed688b9279369da0a4ad78953ae7e6d300436ca8a3264360efe38236e3 // FromPublicKey creates a Payment struct from a btcec.publicKey -func FromPublicKey(pubkey *btcec.PublicKey, net *network.Network) *Payment { +func FromPublicKey( + pubkey *btcec.PublicKey, + net *network.Network, + blindingKey *btcec.PublicKey, +) *Payment { var tmpNet *network.Network if net == nil { tmpNet = &network.Liquid @@ -40,11 +45,16 @@ func FromPublicKey(pubkey *btcec.PublicKey, net *network.Network) *Payment { script := make([]byte, 0) script = append([]byte{txscript.OP_0, byte(len(pkHash))}, pkHash...) witnessHash := sha256.Sum256(script) - return &Payment{tmpNet, pubkey, pkHash, nil, nil, script, witnessHash[:]} + return &Payment{tmpNet, pubkey, pkHash, blindingKey, nil, script, witnessHash[:]} } // FromPublicKeys creates a multi-signature Payment struct from list of public key's -func FromPublicKeys(pubkeys []*btcec.PublicKey, nrequired int, net *network.Network) (*Payment, error) { +func FromPublicKeys( + pubkeys []*btcec.PublicKey, + nrequired int, + net *network.Network, + blindingKey *btcec.PublicKey, +) (*Payment, error) { if len(pubkeys) < nrequired { errorMsg := fmt.Sprintf("unable to generate multisig script with "+ "%d required signatures when there are only %d public "+ @@ -71,7 +81,12 @@ func FromPublicKeys(pubkeys []*btcec.PublicKey, nrequired int, net *network.Netw return nil, err } - return FromScript(multiSigScript, tmpNet) + redeem, err := FromScript(multiSigScript, tmpNet, blindingKey) + if err != nil { + return nil, err + } + + return FromPayment(redeem) } // FromPayment creates a Payment struct from a another Payment @@ -79,13 +94,33 @@ func FromPayment(payment *Payment) (*Payment, error) { if payment.Script == nil || len(payment.Script) == 0 { return nil, errors.New("payment's script can't be empty or nil") } - redeem := &Payment{payment.Network, payment.PublicKey, payment.Hash, payment.BlindingKey, payment.Redeem, payment.Script, payment.WitnessHash} + redeem := &Payment{ + payment.Network, + payment.PublicKey, + payment.Hash, + payment.BlindingKey, + payment.Redeem, + payment.Script, + payment.WitnessHash, + } witnessHash := sha256.Sum256(redeem.Script) - return &Payment{payment.Network, payment.PublicKey, hash160(redeem.Script), payment.BlindingKey, redeem, payment.Script, witnessHash[:]}, nil + return &Payment{ + payment.Network, + payment.PublicKey, + hash160(payment.Script), + payment.BlindingKey, + redeem, + payment.Script, + witnessHash[:], + }, nil } // FromPayment creates a nested Payment struct from script -func FromScript(script []byte, net *network.Network) (*Payment, error) { +func FromScript( + script []byte, + net *network.Network, + blindingKey *btcec.PublicKey, +) (*Payment, error) { if script == nil || len(script) == 0 { return nil, errors.New("payment's script can't be empty or nil") } @@ -95,8 +130,21 @@ func FromScript(script []byte, net *network.Network) (*Payment, error) { } else { tmpNet = net } - redeem := &Payment{Network: tmpNet, Script: script} - return FromPayment(redeem) + + scriptHash := make([]byte, 0) + if script[0] == txscript.OP_0 { + scriptHash = append(scriptHash, script[2:]...) + } + if script[0] == txscript.OP_HASH160 { + scriptHash = append(scriptHash, script[2:len(script)-1]...) + } + + return &Payment{ + Network: tmpNet, + Hash: scriptHash, + Script: script, + BlindingKey: blindingKey, + }, nil } // PubKeyHash is a method of the Payment struct to derive a base58 p2pkh address @@ -109,6 +157,27 @@ func (p *Payment) PubKeyHash() string { return addr } +// ConfidentialPubKeyHash is a method of the Payment struct to derive a +//base58 confidential p2pkh address +func (p *Payment) ConfidentialPubKeyHash() string { + if p.Hash == nil || len(p.Hash) == 0 { + errors.New("payment's hash can't be empty or nil") + } + if p.BlindingKey == nil { + errors.New("payment's blinding key can't be nil") + } + + prefix := [1]byte{p.Network.PubKeyHash} + confidentialAddress := append( + append( + prefix[:], + p.BlindingKey.SerializeCompressed()..., + ), + p.Hash..., + ) + return base58.CheckEncode(confidentialAddress, p.Network.Confidential) +} + // ScriptHash is a method of the Payment struct to derive a base58 p2sh address func (p *Payment) ScriptHash() (string, error) { if p.Hash == nil || len(p.Hash) == 0 { @@ -119,6 +188,27 @@ func (p *Payment) ScriptHash() (string, error) { return addr, nil } +// ConfidentialScriptHash is a method of the Payment struct to derive a +//base58 confidential p2sh address +func (p *Payment) ConfidentialScriptHash() string { + if p.Hash == nil || len(p.Hash) == 0 { + errors.New("payment's hash can't be empty or nil") + } + if p.BlindingKey == nil { + errors.New("payment's blinding key can't be nil") + } + + prefix := [1]byte{p.Network.ScriptHash} + confidentialAddress := append( + append( + prefix[:], + p.BlindingKey.SerializeCompressed()..., + ), + p.Hash..., + ) + return base58.CheckEncode(confidentialAddress, p.Network.Confidential) +} + // WitnessPubKeyHash is a method of the Payment struct to derive a base58 p2wpkh address func (p *Payment) WitnessPubKeyHash() (string, error) { if p.Hash == nil || len(p.Hash) == 0 { @@ -134,6 +224,27 @@ func (p *Payment) WitnessPubKeyHash() (string, error) { return addr, nil } +// ConfidentialWitnessPubKeyHash is a method of the Payment struct to derive +//a confidential blech32 p2wpkh address +func (p *Payment) ConfidentialWitnessPubKeyHash() (string, error) { + if p.Hash == nil || len(p.Hash) == 0 { + return "", errors.New("payment's hash can't be empty or nil") + } + //Here the Version for wpkh is always 0 + version := byte(0x00) + payload := &address.Blech32{ + p.Network.Blech32, + version, + p.BlindingKey.SerializeCompressed(), + p.Hash, + } + addr, err := address.ToBlech32(payload) + if err != nil { + return "", nil + } + return addr, nil +} + // WitnessScriptHash is a method of the Payment struct to derive a base58 p2wsh address func (p *Payment) WitnessScriptHash() (string, error) { if p.Script == nil || len(p.Script) == 0 { @@ -152,6 +263,27 @@ func (p *Payment) WitnessScriptHash() (string, error) { return addr, nil } +// ConfidentialWitnessScriptHash is a method of the Payment struct to derive +//a confidential blech32 p2wsh address +func (p *Payment) ConfidentialWitnessScriptHash() (string, error) { + if p.Hash == nil || len(p.Hash) == 0 { + return "", errors.New("payment's hash can't be empty or nil") + } + //Here the Version for wpkh is always 0 + version := byte(0x00) + payload := &address.Blech32{ + p.Network.Blech32, + version, + p.BlindingKey.SerializeCompressed(), + p.Hash, + } + addr, err := address.ToBlech32(payload) + if err != nil { + return "", nil + } + return addr, nil +} + // Calculate the hash of hasher over buf. func calcHash(buf []byte, hasher hash.Hash) []byte { hasher.Write(buf) diff --git a/payment/payment_test.go b/payment/payment_test.go index becdae4..dfa3af6 100644 --- a/payment/payment_test.go +++ b/payment/payment_test.go @@ -3,6 +3,7 @@ package payment_test import ( "encoding/hex" "github.com/btcsuite/btcd/btcec" + "github.com/stretchr/testify/assert" "github.com/vulpemventures/go-elements/network" "github.com/vulpemventures/go-elements/payment" "testing" @@ -19,7 +20,7 @@ var privateKeyBytes2, _ = hex.DecodeString(privKeyHex2) func TestLegacyAddress(t *testing.T) { _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes1) - pay := payment.FromPublicKey(publicKey, &network.Regtest) + pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) if pay.PubKeyHash() != "2dxEMfPLNa6rZRAfPe7wNWoaUptyBzQ2Zva" { t.Errorf("TestLegacyAddress: error when encoding legacy") } @@ -28,7 +29,7 @@ func TestLegacyAddress(t *testing.T) { func TestSegwitAddress(t *testing.T) { _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes1) - pay := payment.FromPublicKey(publicKey, &network.Regtest) + pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) p2pkh, err := pay.WitnessPubKeyHash() if err != nil { t.Error(err) @@ -40,7 +41,7 @@ func TestSegwitAddress(t *testing.T) { func TestScriptHash(t *testing.T) { _, publicKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes1) - p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest) + p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest, nil) pay, err := payment.FromPayment(p2wpkh) p2sh, err := pay.ScriptHash() if err != nil { @@ -58,7 +59,7 @@ func TestP2WSH(t *testing.T) { t.Error(err) } - p2ms, err := payment.FromScript(redeemScriptBytes, &network.Regtest) + p2ms, err := payment.FromScript(redeemScriptBytes, &network.Regtest, nil) if err != nil { t.Error(err) } @@ -81,7 +82,12 @@ func TestFromPublicKeys(t *testing.T) { _, publicKey1 := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes1) _, publicKey2 := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes2) - p2ms, err := payment.FromPublicKeys([]*btcec.PublicKey{publicKey1, publicKey2}, 1, &network.Regtest) + p2ms, err := payment.FromPublicKeys( + []*btcec.PublicKey{publicKey1, publicKey2}, + 1, + &network.Regtest, + nil, + ) if err != nil { t.Error(err) } @@ -111,5 +117,123 @@ func TestFromPublicKeys(t *testing.T) { if p2sh != "XJkohBHRMT8JUknSqCH7aJP9gAuAe9eNLY" { t.Errorf("TestScriptHash: error when encoding script hash") } +} + +func TestPaymentConfidentialPubKeyHash(t *testing.T) { + expected := "VTpzxkqVGbraaCz18fQ2GxLvZkupCi2MPtUdt9ygAEeZ8v9gZPtkD5RUc" + + "ap55WZ3aVsbUG6TsQvXc8R3" + pk1 := "030000000000000000000000000000000000000000000000000000000000000001" + pk1Byte, err := hex.DecodeString(pk1) + if err != nil { + t.Fatal(err) + } + pubKey1, err := btcec.ParsePubKey(pk1Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + pk2 := "030000000000000000000000000000000000000000000000000000000000000001" + pk2Byte, err := hex.DecodeString(pk2) + if err != nil { + t.Fatal(err) + } + pubKey2, err := btcec.ParsePubKey(pk2Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + payment := payment.FromPublicKey(pubKey1, &network.Liquid, pubKey2) + assert.Equal(t, expected, payment.ConfidentialPubKeyHash()) +} + +func TestPaymentConfidentialScriptHash(t *testing.T) { + expected := "VJLCUu2hpcjPaTGMnANXni8wVYjsCAiTEznE5zgRZZyAWXE2P6rz6Dvph" + + "BHSn7iz4w9sLb3mFSHGJbte" + script, err := hex.DecodeString( + "a9149f840a5fc02407ef0ad499c2ec0eb0b942fb008687") + if err != nil { + t.Fatal(err) + } + + pk1 := "030000000000000000000000000000000000000000000000000000000000000001" + pk2Byte, err := hex.DecodeString(pk1) + if err != nil { + t.Fatal(err) + } + blindingKey, err := btcec.ParsePubKey(pk2Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + payment, err := payment.FromScript(script, &network.Liquid, blindingKey) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, expected, payment.ConfidentialScriptHash()) + +} + +func TestPaymentConfidentialWitnessPubKeyHash(t *testing.T) { + expected := "lq1qqvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz95" + + "tny4ul3zq2qcskw55h5rhzymdpv5dzw6hr8jz3tq5y" + pk1 := "030000000000000000000000000000000000000000000000000000000000000001" + pk1Byte, err := hex.DecodeString(pk1) + if err != nil { + t.Fatal(err) + } + pubKey1, err := btcec.ParsePubKey(pk1Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + pk2 := "030000000000000000000000000000000000000000000000000000000000000001" + pk2Byte, err := hex.DecodeString(pk2) + if err != nil { + t.Fatal(err) + } + pubKey2, err := btcec.ParsePubKey(pk2Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + payment := payment.FromPublicKey(pubKey1, &network.Liquid, pubKey2) + address, err := payment.ConfidentialWitnessPubKeyHash() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, expected, address) +} + +func TestConfidentialWitnessScriptHash(t *testing.T) { + expected := "lq1qqvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + + "r5x3lrzmrq2mc3c6aa85wgxxfm9v8r062qwq4ty579p54pn2q2hq6f9r3gz0h4tn" + + script, err := hex.DecodeString("0014d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0") + if err != nil { + t.Fatal(err) + } + + pk1 := "030000000000000000000000000000000000000000000000000000000000000001" + pk2Byte, err := hex.DecodeString(pk1) + if err != nil { + t.Fatal(err) + } + blindingKey, err := btcec.ParsePubKey(pk2Byte, btcec.S256()) + if err != nil { + t.Fatal(err) + } + + p, err := payment.FromScript(script, &network.Liquid, blindingKey) + if err != nil { + t.Fatal(err) + } + + addr, err := p.ConfidentialWitnessScriptHash() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, expected, addr) } diff --git a/pset/blinder_test.go b/pset/blinder_test.go index 37d0f70..600ee90 100644 --- a/pset/blinder_test.go +++ b/pset/blinder_test.go @@ -32,7 +32,7 @@ func TestCreateBlindAndBroadcast(t *testing.T) { t.Fatal(err) } pubkey := privkey.PubKey() - p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest) + p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, nil) address, _ := p2wpkh.WitnessPubKeyHash() // Fund sender address. diff --git a/pset/pset_test.go b/pset/pset_test.go index cb0da83..b935f3a 100644 --- a/pset/pset_test.go +++ b/pset/pset_test.go @@ -320,7 +320,7 @@ func TestFromCreateToBroadcast(t *testing.T) { t.Fatal(err) } pubkey := privkey.PubKey() - p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest) + p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, nil) address, _ := p2wpkh.WitnessPubKeyHash() // Fund sender address.