Skip to content

Commit

Permalink
Zero asset issuance (#194)
Browse files Browse the repository at this point in the history
* added factory method FromPublicKeys for creating multiscript payment

* zero asset issuance

* fix test

* refactor after review

* refactor after review

* refactor after review
  • Loading branch information
sekulicd authored May 26, 2022
1 parent 4bc7702 commit 17f0c89
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 38 deletions.
19 changes: 15 additions & 4 deletions confidential/confidential.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion confidential/confidential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ func TestRangeProof(t *testing.T) {
ValueBlindFactor: valueBlindingFactor32,
ValueCommit: valueCommitment,
ScriptPubkey: scriptPubkey,
MinValue: 1,
Exp: 0,
MinBits: 36,
}
Expand Down
4 changes: 4 additions & 0 deletions elementsutil/elementsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ func ReverseBytes(buf []byte) []byte {
}
return tmp
}

func ValidElementValue(val []byte) bool {
return len(val) == 9 && val[0] == byte(1)
}
20 changes: 10 additions & 10 deletions pset/blinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -519,7 +522,6 @@ func (b *Blinder) createBlindedOutputs(
ValueBlindFactor: outVbf,
ValueCommit: valueCommitment[:],
ScriptPubkey: outputScript,
MinValue: 1,
Exp: 0,
MinBits: 52,
}
Expand Down Expand Up @@ -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(
Expand All @@ -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,
}
Expand Down Expand Up @@ -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,
}
Expand Down
227 changes: 227 additions & 0 deletions pset/pset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 17f0c89

Please sign in to comment.