Skip to content

Commit 4dcdb21

Browse files
committed
multi: add challenge to ownership proof generation
1 parent 8fcd27c commit 4dcdb21

File tree

6 files changed

+128
-13
lines changed

6 files changed

+128
-13
lines changed

address/address.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/btcsuite/btcd/chaincfg/chainhash"
1515
"github.com/btcsuite/btcd/txscript"
1616
"github.com/btcsuite/btcd/wire"
17+
"github.com/decred/dcrd/dcrec/secp256k1/v4"
1718
"github.com/lightninglabs/taproot-assets/asset"
1819
"github.com/lightninglabs/taproot-assets/commitment"
1920
"github.com/lightninglabs/taproot-assets/fn"
@@ -517,3 +518,48 @@ func DecodeAddress(addr string, net *ChainParams) (*Tap, error) {
517518

518519
return &a, nil
519520
}
521+
522+
// GenChallengeNUMS generates a variant of the NUMS script key that is modified
523+
// by the provided challenge.
524+
//
525+
// The resulting scriptkey is:
526+
// res := NUMS + challenge*G
527+
func GenChallengeNUMS(challengeBytesOpt fn.Option[[32]byte]) asset.ScriptKey {
528+
var (
529+
nums, g, res btcec.JacobianPoint
530+
challenge secp256k1.ModNScalar
531+
)
532+
533+
if challengeBytesOpt.IsNone() {
534+
return asset.NUMSScriptKey
535+
}
536+
537+
var challengeBytes [32]byte
538+
539+
challengeBytesOpt.WhenSome(func(b [32]byte) {
540+
challengeBytes = b
541+
})
542+
543+
// Convert the NUMS key to a Jacobian point.
544+
asset.NUMSPubKey.AsJacobian(&nums)
545+
546+
// Multiply G by 1 to get G as a Jacobian point.
547+
secp256k1.ScalarBaseMultNonConst(
548+
new(secp256k1.ModNScalar).SetInt(1), &g,
549+
)
550+
551+
// Convert the challenge to a scalar.
552+
challenge.SetByteSlice(challengeBytes[:])
553+
554+
// Calculate res = challenge * G.
555+
secp256k1.ScalarMultNonConst(&challenge, &g, &res)
556+
557+
// Calculate res = nums + res.
558+
secp256k1.AddNonConst(&nums, &res, &res)
559+
560+
res.ToAffine()
561+
562+
resultPubKey := btcec.NewPublicKey(&res.X, &res.Y)
563+
564+
return asset.NewScriptKey(resultPubKey)
565+
}

address/address_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/btcsuite/btcd/chaincfg"
1111
"github.com/btcsuite/btcd/txscript"
1212
"github.com/btcsuite/btcd/wire"
13+
"github.com/decred/dcrd/dcrec/secp256k1/v4"
1314
"github.com/lightninglabs/taproot-assets/asset"
1415
"github.com/lightninglabs/taproot-assets/commitment"
1516
"github.com/lightninglabs/taproot-assets/fn"
@@ -477,6 +478,64 @@ func TestBIPTestVectors(t *testing.T) {
477478
}
478479
}
479480

481+
// TestGenChallengeNUMS tests the generation of NUMS challenges.
482+
func TestGenChallengeNUMS(t *testing.T) {
483+
t.Parallel()
484+
485+
gx, gy := secp256k1.Params().Gx, secp256k1.Params().Gy
486+
487+
// addG is a helper function that adds G to the given public key.
488+
addG := func(p *btcec.PublicKey) *btcec.PublicKey {
489+
x, y := secp256k1.S256().Add(p.X(), p.Y(), gx, gy)
490+
var xFieldVal, yFieldVal secp256k1.FieldVal
491+
xFieldVal.SetByteSlice(x.Bytes())
492+
yFieldVal.SetByteSlice(y.Bytes())
493+
return btcec.NewPublicKey(&xFieldVal, &yFieldVal)
494+
}
495+
496+
testCases := []struct {
497+
name string
498+
challenge fn.Option[[32]byte]
499+
expectedKey asset.ScriptKey
500+
}{
501+
{
502+
name: "no challenge",
503+
challenge: fn.None[[32]byte](),
504+
expectedKey: asset.NUMSScriptKey,
505+
},
506+
{
507+
name: "challenge is scalar 1",
508+
challenge: fn.Some([32]byte{
509+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
510+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
511+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
512+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
513+
}),
514+
expectedKey: asset.NewScriptKey(addG(asset.NUMSPubKey)),
515+
},
516+
{
517+
name: "challenge is scalar 2",
518+
challenge: fn.Some([32]byte{
519+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
520+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
521+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
522+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
523+
}),
524+
expectedKey: asset.NewScriptKey(
525+
addG(addG(asset.NUMSPubKey)),
526+
),
527+
},
528+
}
529+
530+
for _, tc := range testCases {
531+
result := GenChallengeNUMS(tc.challenge)
532+
require.Equal(
533+
t, tc.expectedKey.PubKey.SerializeCompressed(),
534+
result.PubKey.SerializeCompressed(),
535+
)
536+
}
537+
}
538+
480539
// runBIPTestVector runs the tests in a single BIP test vector file.
481540
func runBIPTestVector(t *testing.T, testVectors *TestVectors) {
482541
for _, validCase := range testVectors.ValidTestCases {

proof/verifier.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import (
1313
"github.com/btcsuite/btcd/txscript"
1414
"github.com/btcsuite/btcd/wire"
1515
"github.com/davecgh/go-spew/spew"
16+
"github.com/lightninglabs/taproot-assets/address"
1617
"github.com/lightninglabs/taproot-assets/asset"
1718
"github.com/lightninglabs/taproot-assets/commitment"
19+
"github.com/lightninglabs/taproot-assets/fn"
1820
"github.com/lightninglabs/taproot-assets/vm"
1921
"golang.org/x/exp/maps"
2022
"golang.org/x/sync/errgroup"
@@ -347,9 +349,10 @@ func (p *Proof) verifyChallengeWitness(ctx context.Context,
347349

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

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

372375
outputAsset := ownedAsset.Copy()
373-
outputAsset.ScriptKey = asset.NUMSScriptKey
376+
outputAsset.ScriptKey = address.GenChallengeNUMS(challengeBytes)
374377
outputAsset.PrevWitnesses = []asset.Witness{{
375378
PrevID: &prevId,
376379
}}

tapchannel/aux_funding_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ func (f *FundingController) sendInputOwnershipProofs(peerPub btcec.PublicKey,
719719
// First, we'll grab the proof for the asset input, then
720720
// generate the challenge witness to place in the proof so it
721721
challengeWitness, err := f.cfg.AssetWallet.SignOwnershipProof(
722-
assetInput.Asset(),
722+
assetInput.Asset(), fn.None[[32]byte](),
723723
)
724724
if err != nil {
725725
return fmt.Errorf("error signing ownership proof: %w",

tapfreighter/wallet.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ type Wallet interface {
110110
// SignOwnershipProof creates and signs an ownership proof for the given
111111
// owned asset. The ownership proof consists of a valid witness of a
112112
// signed virtual packet that spends the asset fully to the NUMS key.
113-
SignOwnershipProof(ownedAsset *asset.Asset) (wire.TxWitness, error)
113+
// A challenge may be accepted, which modifies the NUMS key, binding the
114+
// ownership proof to the provided challenge.
115+
SignOwnershipProof(ownedAsset *asset.Asset,
116+
challenge fn.Option[[32]byte]) (wire.TxWitness, error)
114117

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

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

14311434
vPkt := tappsbt.OwnershipProofPacket(
1432-
ownedAsset.Copy(), f.cfg.ChainParams,
1435+
ownedAsset.Copy(), challenge, f.cfg.ChainParams,
14331436
)
14341437
err := tapsend.SignVirtualTransaction(
14351438
vPkt, f.cfg.Signer, f.cfg.WitnessValidator,

tappsbt/create.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,16 @@ func AddOutput(pkt *VPacket, amount uint64, scriptAddr asset.ScriptKey,
139139

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

147-
prevId, outputAsset := proof.CreateOwnershipProofAsset(ownedAsset)
149+
prevId, outputAsset := proof.CreateOwnershipProofAsset(
150+
ownedAsset, challengeBytes,
151+
)
148152
vPkt := &VPacket{
149153
Inputs: []*VInput{{
150154
PrevID: prevId,
@@ -155,7 +159,7 @@ func OwnershipProofPacket(ownedAsset *asset.Asset,
155159
Amount: outputAsset.Amount,
156160
Interactive: true,
157161
AnchorOutputIndex: 0,
158-
ScriptKey: asset.NUMSScriptKey,
162+
ScriptKey: outputAsset.ScriptKey,
159163
}},
160164
ChainParams: chainParams,
161165
Version: V1,

0 commit comments

Comments
 (0)