Skip to content

Commit

Permalink
Add support for asset re-issuance + Refactor tests (#145)
Browse files Browse the repository at this point in the history
* Retrieve network for address within DecodeType

* Add support for reissuance

* Fix AddIssuance method and Add AddReissuance

* Fix blinder to correctly blind also reissuance txs

* Refactor tests:
* Add unique test for blinded issuance and reissuance
* Remove nonsense test
* Add more tests
* Increase readibility

* Make elementsutil importable
  • Loading branch information
altafan authored Nov 20, 2020
1 parent c9c23e4 commit 158cf9c
Show file tree
Hide file tree
Showing 17 changed files with 1,293 additions and 884 deletions.
59 changes: 32 additions & 27 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func FromConfidential(address string) (*ConfidentialAddress, error) {
return nil, err
}

addressType, err := DecodeType(address, *net)
addressType, err := DecodeType(address)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -414,8 +414,8 @@ func NetworkForAddress(address string) (*network.Network, error) {

//ToOutputScript creates a new script to pay a transaction output to a the
//specified address
func ToOutputScript(address string, net network.Network) ([]byte, error) {
addressType, err := DecodeType(address, net)
func ToOutputScript(address string) ([]byte, error) {
addressType, err := DecodeType(address)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -494,6 +494,7 @@ func ToOutputScript(address string, net network.Network) ([]byte, error) {
}
}

// GetScriptType returns the type of the given script (p2pkh, p2sh, etc.)
func GetScriptType(script []byte) int {
switch script[0] {
case txscript.OP_0:
Expand All @@ -511,26 +512,38 @@ func GetScriptType(script []byte) int {
}

//DecodeType returns address type
func DecodeType(address string, net network.Network) (int, error) {
if isBlech32(address, net) {
return decodeBlech32(address, net)
func DecodeType(address string) (int, error) {
net, err := NetworkForAddress(address)
if err != nil {
return -1, err
}

if isBlech32(address, *net) {
return decodeBlech32(address, *net)
}
if isBech32(address, net) {
return decodeBech32(address, net)
if isBech32(address, *net) {
return decodeBech32(address, *net)
}
return decodeBase58(address, net)
return decodeBase58(address, *net)
}

func isBlech32(address string, net network.Network) bool {
oneIndex := strings.LastIndexByte(address, '1')
if oneIndex > 1 {
prefix := address[:oneIndex]
if prefix == net.Blech32 {
return true
}
return false
// IsConfidential checks whether the given address is confidential
func IsConfidential(address string) (bool, error) {
addressType, err := DecodeType(address)
if err != nil {
return false, err
}
return false

isConfidential := (addressType == ConfidentialP2Pkh ||
addressType == ConfidentialP2Sh ||
addressType == ConfidentialP2Wpkh ||
addressType == ConfidentialP2Wsh)

return isConfidential, nil
}

func isBlech32(address string, net network.Network) bool {
return strings.HasPrefix(address, net.Blech32)
}

func decodeBlech32(address string, net network.Network) (int, error) {
Expand All @@ -549,15 +562,7 @@ func decodeBlech32(address string, net network.Network) (int, error) {
}

func isBech32(address string, net network.Network) bool {
oneIndex := strings.LastIndexByte(address, '1')
if oneIndex > 1 {
prefix := address[:oneIndex]
if prefix == net.Bech32 {
return true
}
return false
}
return false
return strings.HasPrefix(address, net.Bech32)
}

func decodeBech32(address string, net network.Network) (int, error) {
Expand Down
12 changes: 1 addition & 11 deletions address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/vulpemventures/go-elements/network"
)

func TestBase58(t *testing.T) {
Expand Down Expand Up @@ -102,53 +101,44 @@ func TestConfidential(t *testing.T) {
func TestDecodeAddressType(t *testing.T) {
tests := []struct {
address string
network network.Network
expectedType int
}{
{
address: "Q9863Eah5byyxdBX8zghpooS2x4Ey8XZyc",
network: network.Liquid,
expectedType: P2Pkh,
},
{
address: "H5RCjtzndKyzFnVe41yg62T3WViWguyz4M",
network: network.Liquid,
expectedType: P2Sh,
},
{
address: "ex1qlg343tpldc4wvjxn3jdq2qs35r8j5yd5vqrmu3",
network: network.Liquid,
expectedType: P2Wpkh,
},
{
address: "ert1q2z45rh444qmeand48lq0wp3jatxs2nzh492ds9s5yscv2pplxwesajz7q3",
network: network.Regtest,
expectedType: P2Wsh,
},
{
address: "VTpuLYhJwE8CFm6h1A6DASCaJuRQqkBt6qGfbebSHAUxGXsJMo8wtRvLZYZSWWXt89jG55pCF4YfxMjh",
network: network.Liquid,
expectedType: ConfidentialP2Pkh,
},
{
address: "VJLDHFUbw8oPUcwzmf9jw4tZdN57rEfAusRmWy6knHAF2a4rLGenJz5WPVuyggVzQPHY6JjzKuw31B6e",
network: network.Liquid,
expectedType: ConfidentialP2Sh,
},
{
address: "lq1qqwrdmhm69vsq3qfym06tlyhfze9ltauay9tv4r34ueplfwtjx0q27dk2c4d3a9ms6wum04efclqph7dg4unwcmwmw4vnqreq3",
network: network.Liquid,
expectedType: ConfidentialP2Wpkh,
},
{
address: "lq1qq2akvug2el2rg6lt6aewh9rzy7dglf9ajdmrkknnwwl3jwxgfkh985x3lrzmrq2mc3c6aa85wgxxfm9v8r062qwq4ty579p54pn2q2hqnhgwv394ycf8",
network: network.Liquid,
expectedType: ConfidentialP2Wsh,
},
}

for _, tt := range tests {
addressType, err := DecodeType(tt.address, tt.network)
addressType, err := DecodeType(tt.address)
if err != nil {
t.Fatal(err)
}
Expand Down
8 changes: 4 additions & 4 deletions confidential/confidential.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,14 @@ func unblindIssuance(
if len(blindKeys) <= 1 {
return nil, errors.New("missing asset blind private key")
}
if in.Issuance == nil {
if !in.HasAnyIssuance() {
return nil, errors.New("missing input issuance")
}
if len(in.IssuanceRangeProof) <= 0 {
if !in.HasConfidentialIssuance() {
return nil, errors.New("missing asset range proof")
}

if len(in.Issuance.TokenAmount) > 0 {
if in.Issuance.HasTokenAmount() {
if len(in.InflationRangeProof) <= 0 {
return nil, errors.New("missing token range proof")
}
Expand All @@ -267,7 +267,7 @@ func unblindIssuance(
Script: make([]byte, 0),
},
}
if len(in.Issuance.TokenAmount) > 0 {
if in.Issuance.HasTokenAmount() {
token, err := calcTokenHash(in)
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ The input we just funded from the faucet and three outputs.
One for the ammount we want to send, one for the change and a last one for the fee.
txInputHash, _ := hex.DecodeString(utxos[0]["txid"].(string))
txInputHash = bufferutil.ReverseBytes(txInputHash)
txInputHash = elementsutil.ReverseBytes(txInputHash)
txInputIndex := uint32(utxos[0]["vout"].(float64))
txInput := transaction.NewTxInput(txInputHash, txInputIndex)
lbtc, _ := hex.DecodeString(
"5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225",
)
lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...)
lbtc = append([]byte{0x01}, elementsutil.ReverseBytes(lbtc)...)
receiverValue, _ := confidential.SatoshiToElementsValue(60000000)
receiverScript, _ := hex.DecodeString("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac")
receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript)
Expand Down
18 changes: 16 additions & 2 deletions internal/elementsutil/amount.go → elementsutil/elementsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func SatoshiToElementsValue(val uint64) ([]byte, error) {
if err := bufferutil.BinarySerializer.PutUint64(b, binary.LittleEndian, val); err != nil {
return nil, err
}
res := append([]byte{unconfPrefix}, bufferutil.ReverseBytes(b.Bytes())...)
res := append([]byte{unconfPrefix}, ReverseBytes(b.Bytes())...)
return res, nil
}

Expand All @@ -27,7 +27,21 @@ func ElementsToSatoshiValue(val []byte) (uint64, error) {
if val[0] != byte(1) {
return 0, errors.New("invalid prefix")
}
reverseValueBuffer := bufferutil.ReverseBytes(val[1:])
reverseValueBuffer := ReverseBytes(val[1:])
d := bufferutil.NewDeserializer(bytes.NewBuffer(reverseValueBuffer))
return d.ReadUint64()
}

// ReverseBytes returns a copy of the given byte slice with elems in reverse order.
func ReverseBytes(buf []byte) []byte {
if len(buf) < 1 {
return buf
}
tmp := make([]byte, len(buf))
copy(tmp, buf)
for i := len(tmp)/2 - 1; i >= 0; i-- {
j := len(tmp) - 1 - i
tmp[i], tmp[j] = tmp[j], tmp[i]
}
return tmp
}
File renamed without changes.
14 changes: 0 additions & 14 deletions internal/bufferutil/bufferutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,17 +289,3 @@ func VarIntSerializeSize(val uint64) int {
func VarSliceSerializeSize(val []byte) int {
return VarIntSerializeSize(uint64(len(val))) + len(val)
}

// ReverseBytes returns a copy of the given byte slice with elems in reverse order.
func ReverseBytes(buf []byte) []byte {
if len(buf) < 1 {
return buf
}
tmp := make([]byte, len(buf))
copy(tmp, buf)
for i := len(tmp)/2 - 1; i >= 0; i-- {
j := len(tmp) - 1 - i
tmp[i], tmp[j] = tmp[j], tmp[i]
}
return tmp
}
49 changes: 25 additions & 24 deletions pset/blinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/btcsuite/btcd/btcec"
"github.com/vulpemventures/go-elements/confidential"
"github.com/vulpemventures/go-elements/internal/elementsutil"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/transaction"
)

Expand Down Expand Up @@ -180,9 +180,12 @@ func (b *blinder) unblindInputs() (

// if the current input contains an issuance, add the pseudo input to the
// returned unblindedPseudoIns array
if input.HasIssuance() {
issuance := transaction.NewTxIssuanceFromContractHash(input.Issuance.AssetEntropy)
issuance.GenerateEntropy(input.Hash, input.Index)
if input.HasAnyIssuance() {
issuance, err := transaction.NewTxIssuanceFromInput(input)
if err != nil {
return nil, nil, err
}

asset, err := issuance.GenerateAsset()
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -211,9 +214,10 @@ func (b *blinder) unblindInputs() (

// if the token amount is not defined, it is set to 0x00, thus we need
// to check if the input.Issuance.TokenAmount, that is encoded in the
// elements format, contains more than one byte
if len(input.Issuance.TokenAmount) > 1 {
value, err := elementsutil.ElementsToSatoshiValue(input.Issuance.TokenAmount)
// elements format, contains more than one byte. We simply ignore the
// token amount for reissuances.
if i := input.Issuance; !i.IsReissuance() && i.HasTokenAmount() {
value, err := elementsutil.ElementsToSatoshiValue(i.TokenAmount)
if err != nil {
return nil, nil, err
}
Expand All @@ -225,9 +229,7 @@ func (b *blinder) unblindInputs() (
tokenFlag = 0
}

token, err := issuance.GenerateReissuanceToken(
tokenFlag,
)
token, err := issuance.GenerateReissuanceToken(tokenFlag)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -320,23 +322,20 @@ func (b *blinder) blindInputs(unblinded []confidential.UnblindOutputResult) erro

getBlindingFactors := func(asset []byte) ([]byte, []byte, error) {
for _, u := range unblinded {
if bytes.Compare(asset, u.Asset) == 0 {
if bytes.Equal(asset, u.Asset) {
return u.ValueBlindingFactor, u.AssetBlindingFactor, nil
}
}
return nil, nil, errors.New("no blinding factors generated for pseudo issuance inputs")
}

for index, input := range b.pset.UnsignedTx.Inputs {
if input.HasIssuance() {
issuance := transaction.NewTxIssuanceFromContractHash(
input.Issuance.AssetEntropy,
)

err := issuance.GenerateEntropy(input.Hash, input.Index)
if input.HasAnyIssuance() {
issuance, err := transaction.NewTxIssuanceFromInput(input)
if err != nil {
return err
}

asset, err := issuance.GenerateAsset()
if err != nil {
return err
Expand All @@ -352,10 +351,12 @@ func (b *blinder) blindInputs(unblinded []confidential.UnblindOutputResult) erro
return err
}

// if the token amount is not defined, it is set to 0x00, thus we need
// to check if the input.Issuance.TokenAmount, that is encoded in the
// elements format, contains more than one byte
if len(input.Issuance.TokenAmount) > 1 {
// ONLY in case the issuance is not a reissuance, if the token amount is
// not defined, it is set to 0x00, thus it's required to check that the
// input.Issuance.TokenAmount, that's encoded in the elements format (!),
// is longer than one byte. Reissuances, instead, cannot have a token
// amount defined.
if i := input.Issuance; !i.IsReissuance() && i.HasTokenAmount() {
token, err := issuance.GenerateReissuanceToken(
ConfidentialReissuanceTokenFlag,
)
Expand Down Expand Up @@ -686,7 +687,7 @@ func verifyBlinding(
inAssetBlinders = append(inAssetBlinders, unblinded.AssetBlindingFactor)
}

if txIn := pset.UnsignedTx.Inputs[i]; txIn.HasIssuance() {
if txIn := pset.UnsignedTx.Inputs[i]; txIn.HasAnyIssuance() {
if txIn.HasConfidentialIssuance() {
unblinded, err := confidential.UnblindIssuance(txIn, inIssuanceKeys[i].ToSlice())
if err != nil {
Expand All @@ -698,7 +699,7 @@ func verifyBlinding(
unblinded.Asset.AssetBlindingFactor,
)

if len(txIn.Issuance.TokenAmount) > 0 {
if txIn.Issuance.HasTokenAmount() {
inIssuanceAssets = append(inIssuanceAssets, unblinded.Token.Asset)
inIssuanceAssetBlinders = append(
inIssuanceAssetBlinders,
Expand All @@ -720,7 +721,7 @@ func verifyBlinding(
transaction.Zero[:],
)

if len(txIn.Issuance.TokenAmount) > 0 {
if txIn.Issuance.HasTokenAmount() {
token, err := iss.GenerateReissuanceToken(0)
if err != nil {
return false
Expand Down
Loading

0 comments on commit 158cf9c

Please sign in to comment.