diff --git a/confidential/confidential.go b/confidential/confidential.go index 1ea1095..651de8c 100644 --- a/confidential/confidential.go +++ b/confidential/confidential.go @@ -4,10 +4,16 @@ import ( "crypto/sha256" "errors" + "github.com/btcsuite/btcd/txscript" + "github.com/vulpemventures/go-elements/transaction" "github.com/vulpemventures/go-secp256k1-zkp" ) +const ( + maxScriptSize = 10000 +) + // NonceHash method generates hashed secret based on ecdh. func NonceHash(pubKey, privKey []byte) ( result [32]byte, @@ -107,16 +113,21 @@ type RangeProofArgs struct { ValueBlindFactor [32]byte ValueCommit []byte ScriptPubkey []byte - MinValue uint64 Exp int MinBits int } func (a RangeProofArgs) minValue() uint64 { - if a.MinValue <= 0 { - return 1 + if isUnSpendable(a.ScriptPubkey) { + return 0 } - return a.MinValue + + return 1 +} + +func isUnSpendable(script []byte) bool { + return (len(script) > 0 && script[0] == txscript.OP_RETURN) || + (len(script) > maxScriptSize) || (len(script) == 0) } func (a RangeProofArgs) exp() int { diff --git a/confidential/confidential_test.go b/confidential/confidential_test.go index f9d9e28..fffc8b2 100644 --- a/confidential/confidential_test.go +++ b/confidential/confidential_test.go @@ -285,7 +285,6 @@ func TestRangeProof(t *testing.T) { ValueBlindFactor: valueBlindingFactor32, ValueCommit: valueCommitment, ScriptPubkey: scriptPubkey, - MinValue: 1, Exp: 0, MinBits: 36, } diff --git a/elementsutil/elementsutil.go b/elementsutil/elementsutil.go index bb0de20..f500375 100644 --- a/elementsutil/elementsutil.go +++ b/elementsutil/elementsutil.go @@ -45,3 +45,7 @@ func ReverseBytes(buf []byte) []byte { } return tmp } + +func ValidElementValue(val []byte) bool { + return len(val) == 9 && val[0] == byte(1) +} diff --git a/pset/blinder.go b/pset/blinder.go index c30c8db..83f6ea2 100644 --- a/pset/blinder.go +++ b/pset/blinder.go @@ -215,7 +215,10 @@ func (b *Blinder) unblindInputsToIssuanceBlindingData() ( return nil, err } - value, _ := elementsutil.ElementsToSatoshiValue(input.Issuance.AssetAmount) + var value uint64 = 0 + if elementsutil.ValidElementValue(input.Issuance.AssetAmount) { + value, _ = elementsutil.ElementsToSatoshiValue(input.Issuance.AssetAmount) + } // prepare the random asset and value blinding factors in case the // issuance needs to be blinded, otherwise they're set to the 0 byte array @@ -241,9 +244,9 @@ func (b *Blinder) unblindInputsToIssuanceBlindingData() ( // 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, err + var value uint64 = 0 + if elementsutil.ValidElementValue(i.TokenAmount) { + value, _ = elementsutil.ElementsToSatoshiValue(i.TokenAmount) } var tokenFlag uint @@ -519,7 +522,6 @@ func (b *Blinder) createBlindedOutputs( ValueBlindFactor: outVbf, ValueCommit: valueCommitment[:], ScriptPubkey: outputScript, - MinValue: 1, Exp: 0, MinBits: 52, } @@ -573,9 +575,9 @@ func (b *Blinder) blindAsset(index int, asset, vbf, abf []byte) error { return err } - assetAmountSatoshi, err := elementsutil.ElementsToSatoshiValue(assetAmount) - if err != nil { - return err + var assetAmountSatoshi uint64 = 0 + if len(assetAmount) == 9 { + assetAmountSatoshi, _ = elementsutil.ElementsToSatoshiValue(assetAmount) } valueCommitment, err := confidential.ValueCommitment( @@ -601,7 +603,6 @@ func (b *Blinder) blindAsset(index int, asset, vbf, abf []byte) error { ValueBlindFactor: vbf32, ValueCommit: valueCommitment[:], ScriptPubkey: []byte{}, - MinValue: 1, Exp: 0, MinBits: 52, } @@ -654,7 +655,6 @@ func (b *Blinder) blindToken(index int, token, vbf, abf []byte) error { ValueBlindFactor: vbf32, ValueCommit: valueCommitment[:], ScriptPubkey: []byte{}, - MinValue: 1, Exp: 0, MinBits: 52, } diff --git a/pset/pset_test.go b/pset/pset_test.go index 62d67d0..2777bf3 100644 --- a/pset/pset_test.go +++ b/pset/pset_test.go @@ -773,6 +773,98 @@ func TestBroadcastUnblindedIssuanceTx(t *testing.T) { } } +func TestBroadcastUnblindedZeroAssetValueIssuanceTx(t *testing.T) { + /** + * This test attempts to broadcast an issuance transaction composed by 1 + * P2WPKH input and 3 outputs. The input of the transaction will contain a new + * unblinded asset issuance with a defined reissuance token. The outputs will + * be a p2wpkh for both the asset and the relative token and another p2wpkh + * for the change (same of the sender for simplicity). A 4th unblinded output + * is for the fees, that in Elements side chains are explicits. + **/ + + privkey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + pubkey := privkey.PubKey() + p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, nil) + address, _ := p2wpkh.WitnessPubKeyHash() + + // Fund sender address. + if _, err := faucet(address); err != nil { + t.Fatal(err) + } + + // Retrieve sender utxos. + utxos, err := unspents(address) + if err != nil { + t.Fatal(err) + } + + // The transaction will have 1 input and 3 outputs. + txInputHash := elementsutil.ReverseBytes(h2b(utxos[0]["txid"].(string))) + txInputIndex := uint32(utxos[0]["vout"].(float64)) + txInput := transaction.NewTxInput(txInputHash, txInputIndex) + + changeScript := p2wpkh.WitnessScript + changeValue, _ := elementsutil.SatoshiToElementsValue(99999500) + changeOutput := transaction.NewTxOutput(lbtc, changeValue, changeScript) + + feeScript := []byte{} + feeValue, _ := elementsutil.SatoshiToElementsValue(500) + feeOutput := transaction.NewTxOutput(lbtc, feeValue, feeScript) + + // Create a new pset. + inputs := []*transaction.TxInput{txInput} + outputs := []*transaction.TxOutput{changeOutput, feeOutput} + p, err := New(inputs, outputs, 2, 0) + if err != nil { + t.Fatal(err) + } + + updater, err := NewUpdater(p) + if err != nil { + t.Fatal(err) + } + + arg := AddIssuanceArgs{ + Precision: 0, + Contract: &transaction.IssuanceContract{ + Name: "Test", + Ticker: "TST", + Version: 0, + Precision: 0, + Entity: transaction.IssuanceEntity{ + Domain: "test.io", + }, + }, + AssetAmount: 0, + TokenAmount: 1, + AssetAddress: address, + TokenAddress: address, + } + if err := updater.AddIssuance(arg); err != nil { + t.Fatal(err) + } + + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witnessUtxo := transaction.NewTxOutput(lbtc, witValue, p2wpkh.WitnessScript) + if err := updater.AddInWitnessUtxo(witnessUtxo, 0); err != nil { + t.Fatal(err) + } + + prvKeys := []*btcec.PrivateKey{privkey} + scripts := [][]byte{p2wpkh.Script} + if err := signTransaction(p, prvKeys, scripts, true, nil); err != nil { + t.Fatal(err) + } + + if _, err := broadcastTransaction(p); err != nil { + t.Fatal(err) + } +} + func TestBroadcastBlindedTx(t *testing.T) { /** * This test attempts to broadcast a confidential transaction composed by 1 @@ -1203,6 +1295,141 @@ func TestBroadcastIssuanceTxWithBlindedOutput(t *testing.T) { } } +func TestBroadcastBlindedZeroAssetValueIssuanceTx(t *testing.T) { + privkey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + pubkey := privkey.PubKey() + + blindPrivkey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + blindPubkey := blindPrivkey.PubKey() + + p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, blindPubkey) + address, _ := p2wpkh.ConfidentialWitnessPubKeyHash() + + // Fund sender address. + if _, err := faucet(address); err != nil { + t.Fatal(err) + } + + // Retrieve sender utxos. + utxosForIssuanceTx, err := unspents(address) + if err != nil { + t.Fatal(err) + } + + // The transaction will have 1 input and 2 outputs. + txInputHashForIssuanceTx := elementsutil.ReverseBytes( + h2b(utxosForIssuanceTx[0]["txid"].(string)), + ) + txInputIndexForIssuanceTx := uint32(utxosForIssuanceTx[0]["vout"].(float64)) + txInputForIssuanceTx := transaction.NewTxInput( + txInputHashForIssuanceTx, + txInputIndexForIssuanceTx, + ) + + // Create a new pset. + inputsForIssuanceTx := []*transaction.TxInput{txInputForIssuanceTx} + outputsForIssuanceTx := []*transaction.TxOutput{} + issuancePset, err := New(inputsForIssuanceTx, outputsForIssuanceTx, 2, 0) + if err != nil { + t.Fatal(err) + } + + updater, err := NewUpdater(issuancePset) + if err != nil { + t.Fatal(err) + } + + issuanceArgs := AddIssuanceArgs{ + Precision: 0, + AssetAmount: 0, + TokenAmount: 1, + AssetAddress: address, + TokenAddress: address, + } + if err := updater.AddIssuance(issuanceArgs); err != nil { + t.Fatal(err) + } + + txHex, err := fetchTx(utxosForIssuanceTx[0]["txid"].(string)) + if err != nil { + t.Fatal(err) + } + tx, _ := transaction.NewTxFromHex(txHex) + assetCommitmentForIssuanceTx := h2b(utxosForIssuanceTx[0]["assetcommitment"].(string)) + valueCommitmentForIssuanceTx := h2b(utxosForIssuanceTx[0]["valuecommitment"].(string)) + witnessUtxoForIssuanceTx := &transaction.TxOutput{ + Asset: assetCommitmentForIssuanceTx, + Value: valueCommitmentForIssuanceTx, + Script: p2wpkh.WitnessScript, + Nonce: tx.Outputs[txInputIndexForIssuanceTx].Nonce, + RangeProof: tx.Outputs[txInputIndexForIssuanceTx].RangeProof, + SurjectionProof: tx.Outputs[txInputIndexForIssuanceTx].SurjectionProof, + } + if err := updater.AddInWitnessUtxo(witnessUtxoForIssuanceTx, 0); err != nil { + t.Fatal(err) + } + + // Add change and fees + changeScriptForIssuanceTx := p2wpkh.WitnessScript + changeValueForIssuanceTx, _ := elementsutil.SatoshiToElementsValue(99996000) + changeOutputForIssuanceTx := transaction.NewTxOutput( + lbtc, + changeValueForIssuanceTx, + changeScriptForIssuanceTx, + ) + updater.AddOutput(changeOutputForIssuanceTx) + + //blind outputs + inBlindingPrvKeysForIssuance := [][]byte{blindPrivkey.Serialize()} + outBlindingPrvKeysForIssuance := make([][]byte, 2) + for i := range outBlindingPrvKeysForIssuance { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outBlindingPrvKeysForIssuance[i] = pk.Serialize() + } + outBlindingPrvKeysForIssuance = append( + [][]byte{outBlindingPrvKeysForIssuance[0]}, + outBlindingPrvKeysForIssuance..., + ) + + issuanceBlindPrvKeys := []IssuanceBlindingPrivateKeys{ + { + AssetKey: outBlindingPrvKeysForIssuance[1], + TokenKey: outBlindingPrvKeysForIssuance[2], + }, + } + + if err := blindTransaction( + issuancePset, + inBlindingPrvKeysForIssuance, + outBlindingPrvKeysForIssuance, + issuanceBlindPrvKeys, + ); err != nil { + t.Fatal(err) + } + + addFeesToTransaction(issuancePset, 4000) + + prvKeys := []*btcec.PrivateKey{privkey} + scripts := [][]byte{p2wpkh.Script} + if err := signTransaction(issuancePset, prvKeys, scripts, true, nil); err != nil { + t.Fatal(err) + } + + _, err = broadcastTransaction(issuancePset) + if err != nil { + t.Fatal(err) + } +} + func TestBroadcastBlindedIssuanceAndReIssuanceTx(t *testing.T) { /** * This test attempts to broadcast a confidential issuance transaction diff --git a/pset/updater.go b/pset/updater.go index e2d3bb1..57f371a 100644 --- a/pset/updater.go +++ b/pset/updater.go @@ -408,12 +408,14 @@ func (arg AddIssuanceArgs) validate() error { return err } - if len(arg.AssetAddress) <= 0 { - return errors.New("missing destination address for asset to issue") - } + if arg.AssetAmount > 0 { + if len(arg.AssetAddress) <= 0 { + return errors.New("missing destination address for asset to issue") + } - if _, err := address.DecodeType(arg.AssetAddress); err != nil { - return err + if _, err := address.DecodeType(arg.AssetAddress); err != nil { + return err + } } if arg.TokenAmount > 0 { @@ -435,7 +437,7 @@ func (arg AddIssuanceArgs) validate() error { } func (arg AddIssuanceArgs) matchAddressTypes() bool { - if len(arg.TokenAddress) <= 0 { + if len(arg.AssetAddress) <= 0 || len(arg.TokenAddress) <= 0 { return true } @@ -504,12 +506,14 @@ func (p *Updater) AddIssuance(arg AddIssuanceArgs) error { return err } - output := transaction.NewTxOutput( - assetHash, - issuance.TxIssuance.AssetAmount, - script, - ) - p.AddOutput(output) + if arg.AssetAmount > 0 { + output := transaction.NewTxOutput( + assetHash, + issuance.TxIssuance.AssetAmount, + script, + ) + p.AddOutput(output) + } if arg.TokenAmount > 0 { tokenHash, err := issuance.GenerateReissuanceToken(arg.tokenFlag()) diff --git a/transaction/issuance.go b/transaction/issuance.go index ac1f0e5..66f6472 100644 --- a/transaction/issuance.go +++ b/transaction/issuance.go @@ -119,13 +119,14 @@ func NewTxIssuance( contractHash = chainhash.HashB(tmp) } - confAssetAmount, err := toConfidentialAssetAmount( + confAssetAmount, err := toConfidentialIssuanceAmount( assetAmount, ) if err != nil { return nil, err } - confTokenAmount, err := toConfidentialTokenAmount( + + confTokenAmount, err := toConfidentialIssuanceAmount( tokenAmount, ) if err != nil { @@ -204,15 +205,7 @@ func (issuance *TxIssuanceExtended) GenerateReissuanceToken(flag uint) ([]byte, return token[:], nil } -func toConfidentialAssetAmount(assetAmount uint64) ([]byte, error) { - confAmount, err := elementsutil.SatoshiToElementsValue(assetAmount) - if err != nil { - return nil, err - } - return confAmount[:], nil -} - -func toConfidentialTokenAmount(tokenAmount uint64) ([]byte, error) { +func toConfidentialIssuanceAmount(tokenAmount uint64) ([]byte, error) { if tokenAmount == 0 { return []byte{0x00}, nil }