Skip to content

Commit

Permalink
Payment: support for confidential addresses (#86)
Browse files Browse the repository at this point in the history
* init

* Confidential p2sh/p2pkh addresses added

* Confidential p2sh/p2pkh addresses added

* ConfidentialWitnessPubKeyHash added

* confidential payment example added
  • Loading branch information
sekulicd authored Jun 9, 2020
1 parent c0d3b45 commit 52add2f
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 37 deletions.
21 changes: 17 additions & 4 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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")
}

Expand Down
26 changes: 12 additions & 14 deletions address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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")
}
Expand All @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion example.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 22 additions & 2 deletions payment/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,38 @@ 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 {
fmt.Println(err)
}
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)
}
150 changes: 141 additions & 9 deletions payment/payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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 "+
Expand All @@ -71,21 +81,46 @@ 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
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")
}
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 52add2f

Please sign in to comment.