Skip to content

Commit

Permalink
multi: add challenge to ownership proof generation
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgeTsagk committed Aug 15, 2024
1 parent 8fcd27c commit 4dcdb21
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 13 deletions.
46 changes: 46 additions & 0 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
Expand Down Expand Up @@ -517,3 +518,48 @@ func DecodeAddress(addr string, net *ChainParams) (*Tap, error) {

return &a, nil
}

// GenChallengeNUMS generates a variant of the NUMS script key that is modified
// by the provided challenge.
//
// The resulting scriptkey is:
// res := NUMS + challenge*G
func GenChallengeNUMS(challengeBytesOpt fn.Option[[32]byte]) asset.ScriptKey {
var (
nums, g, res btcec.JacobianPoint
challenge secp256k1.ModNScalar
)

if challengeBytesOpt.IsNone() {
return asset.NUMSScriptKey
}

var challengeBytes [32]byte

challengeBytesOpt.WhenSome(func(b [32]byte) {
challengeBytes = b
})

// Convert the NUMS key to a Jacobian point.
asset.NUMSPubKey.AsJacobian(&nums)

// Multiply G by 1 to get G as a Jacobian point.
secp256k1.ScalarBaseMultNonConst(
new(secp256k1.ModNScalar).SetInt(1), &g,
)

// Convert the challenge to a scalar.
challenge.SetByteSlice(challengeBytes[:])

// Calculate res = challenge * G.
secp256k1.ScalarMultNonConst(&challenge, &g, &res)

// Calculate res = nums + res.
secp256k1.AddNonConst(&nums, &res, &res)

res.ToAffine()

resultPubKey := btcec.NewPublicKey(&res.X, &res.Y)

return asset.NewScriptKey(resultPubKey)
}
59 changes: 59 additions & 0 deletions address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
Expand Down Expand Up @@ -477,6 +478,64 @@ func TestBIPTestVectors(t *testing.T) {
}
}

// TestGenChallengeNUMS tests the generation of NUMS challenges.
func TestGenChallengeNUMS(t *testing.T) {
t.Parallel()

gx, gy := secp256k1.Params().Gx, secp256k1.Params().Gy

// addG is a helper function that adds G to the given public key.
addG := func(p *btcec.PublicKey) *btcec.PublicKey {
x, y := secp256k1.S256().Add(p.X(), p.Y(), gx, gy)
var xFieldVal, yFieldVal secp256k1.FieldVal
xFieldVal.SetByteSlice(x.Bytes())
yFieldVal.SetByteSlice(y.Bytes())
return btcec.NewPublicKey(&xFieldVal, &yFieldVal)
}

testCases := []struct {
name string
challenge fn.Option[[32]byte]
expectedKey asset.ScriptKey
}{
{
name: "no challenge",
challenge: fn.None[[32]byte](),
expectedKey: asset.NUMSScriptKey,
},
{
name: "challenge is scalar 1",
challenge: fn.Some([32]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
}),
expectedKey: asset.NewScriptKey(addG(asset.NUMSPubKey)),
},
{
name: "challenge is scalar 2",
challenge: fn.Some([32]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
}),
expectedKey: asset.NewScriptKey(
addG(addG(asset.NUMSPubKey)),
),
},
}

for _, tc := range testCases {
result := GenChallengeNUMS(tc.challenge)
require.Equal(
t, tc.expectedKey.PubKey.SerializeCompressed(),
result.PubKey.SerializeCompressed(),
)
}
}

// runBIPTestVector runs the tests in a single BIP test vector file.
func runBIPTestVector(t *testing.T, testVectors *TestVectors) {
for _, validCase := range testVectors.ValidTestCases {
Expand Down
11 changes: 7 additions & 4 deletions proof/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/taproot-assets/address"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/vm"
"golang.org/x/exp/maps"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -347,9 +349,10 @@ func (p *Proof) verifyChallengeWitness(ctx context.Context,

// CreateOwnershipProofAsset creates a virtual asset that can be used to prove
// ownership of an asset. The virtual asset is created by spending the full
// asset into a NUMS key.
func CreateOwnershipProofAsset(
ownedAsset *asset.Asset) (asset.PrevID, *asset.Asset) {
// asset into a NUMS key. If a challenge is defined, the NUMS key will be
// modified based on that value.
func CreateOwnershipProofAsset(ownedAsset *asset.Asset,
challengeBytes fn.Option[[32]byte]) (asset.PrevID, *asset.Asset) {

// We create the ownership proof by creating a virtual input and output
// that spends the full asset into a NUMS key. But in order to prevent
Expand All @@ -370,7 +373,7 @@ func CreateOwnershipProofAsset(
}

outputAsset := ownedAsset.Copy()
outputAsset.ScriptKey = asset.NUMSScriptKey
outputAsset.ScriptKey = address.GenChallengeNUMS(challengeBytes)
outputAsset.PrevWitnesses = []asset.Witness{{
PrevID: &prevId,
}}
Expand Down
2 changes: 1 addition & 1 deletion tapchannel/aux_funding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ func (f *FundingController) sendInputOwnershipProofs(peerPub btcec.PublicKey,
// First, we'll grab the proof for the asset input, then
// generate the challenge witness to place in the proof so it
challengeWitness, err := f.cfg.AssetWallet.SignOwnershipProof(
assetInput.Asset(),
assetInput.Asset(), fn.None[[32]byte](),
)
if err != nil {
return fmt.Errorf("error signing ownership proof: %w",
Expand Down
11 changes: 7 additions & 4 deletions tapfreighter/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ type Wallet interface {
// SignOwnershipProof creates and signs an ownership proof for the given
// owned asset. The ownership proof consists of a valid witness of a
// signed virtual packet that spends the asset fully to the NUMS key.
SignOwnershipProof(ownedAsset *asset.Asset) (wire.TxWitness, error)
// A challenge may be accepted, which modifies the NUMS key, binding the
// ownership proof to the provided challenge.
SignOwnershipProof(ownedAsset *asset.Asset,
challenge fn.Option[[32]byte]) (wire.TxWitness, error)

// FetchScriptKey attempts to fetch the full tweaked script key struct
// (including the key descriptor) for the given tweaked script key. If
Expand Down Expand Up @@ -1422,14 +1425,14 @@ func (f *AssetWallet) AnchorVirtualTransactions(ctx context.Context,
// SignOwnershipProof creates and signs an ownership proof for the given owned
// asset. The ownership proof consists of a signed virtual packet that spends
// the asset fully to the NUMS key.
func (f *AssetWallet) SignOwnershipProof(
ownedAsset *asset.Asset) (wire.TxWitness, error) {
func (f *AssetWallet) SignOwnershipProof(ownedAsset *asset.Asset,
challenge fn.Option[[32]byte]) (wire.TxWitness, error) {

outputAsset := ownedAsset.Copy()
log.Infof("Generating ownership proof for asset %v", outputAsset.ID())

vPkt := tappsbt.OwnershipProofPacket(
ownedAsset.Copy(), f.cfg.ChainParams,
ownedAsset.Copy(), challenge, f.cfg.ChainParams,
)
err := tapsend.SignVirtualTransaction(
vPkt, f.cfg.Signer, f.cfg.WitnessValidator,
Expand Down
12 changes: 8 additions & 4 deletions tappsbt/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,16 @@ func AddOutput(pkt *VPacket, amount uint64, scriptAddr asset.ScriptKey,

// OwnershipProofPacket creates a virtual transaction packet that is used to
// prove ownership of an asset. It creates a 1-in-1-out transaction that spends
// the owned asset to the NUMS key. The witness is created over an empty
// previous outpoint, so it can never be used in an actual state transition.
// the owned asset to the NUMS key. If a challenge is defined the NUMS key is
// modified based on that value. The witness is created over an empty previous
// outpoint, so it can never be used in an actual state transition.
func OwnershipProofPacket(ownedAsset *asset.Asset,
challengeBytes fn.Option[[32]byte],
chainParams *address.ChainParams) *VPacket {

prevId, outputAsset := proof.CreateOwnershipProofAsset(ownedAsset)
prevId, outputAsset := proof.CreateOwnershipProofAsset(
ownedAsset, challengeBytes,
)
vPkt := &VPacket{
Inputs: []*VInput{{
PrevID: prevId,
Expand All @@ -155,7 +159,7 @@ func OwnershipProofPacket(ownedAsset *asset.Asset,
Amount: outputAsset.Amount,
Interactive: true,
AnchorOutputIndex: 0,
ScriptKey: asset.NUMSScriptKey,
ScriptKey: outputAsset.ScriptKey,
}},
ChainParams: chainParams,
Version: V1,
Expand Down

0 comments on commit 4dcdb21

Please sign in to comment.