Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 30 additions & 38 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ 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"
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightningnetwork/lnd/tlv"
)

Expand Down Expand Up @@ -538,47 +538,39 @@ 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,
)
// UnmarshalVersion parses an address version from the RPC variant.
func UnmarshalVersion(version taprpc.AddrVersion) (Version, error) {
// For now, we'll only support two address versions. The ones in the
// future should be reserved for future use, so we disallow unknown
// versions.
switch version {
case taprpc.AddrVersion_ADDR_VERSION_UNSPECIFIED:
return V1, nil

// Convert the challenge to a scalar.
challenge.SetByteSlice(challengeBytes[:])
case taprpc.AddrVersion_ADDR_VERSION_V0:
return V0, nil

// Calculate res = challenge * G.
secp256k1.ScalarMultNonConst(&challenge, &g, &res)
case taprpc.AddrVersion_ADDR_VERSION_V1:
return V1, nil

// Calculate res = nums + res.
secp256k1.AddNonConst(&nums, &res, &res)
default:
return 0, fmt.Errorf("unknown address version: %v", version)
}
}

res.ToAffine()
// MarshalVersion marshals the native address version into the RPC variant.
func MarshalVersion(version Version) (taprpc.AddrVersion, error) {
// For now, we'll only support two address versions. The ones in the
// future should be reserved for future use, so we disallow unknown
// versions.
switch version {
case V0:
return taprpc.AddrVersion_ADDR_VERSION_V0, nil

resultPubKey := btcec.NewPublicKey(&res.X, &res.Y)
case V1:
return taprpc.AddrVersion_ADDR_VERSION_V1, nil

return asset.NewScriptKey(resultPubKey)
default:
return 0, fmt.Errorf("unknown address version: %v", version)
}
}
59 changes: 0 additions & 59 deletions address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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 @@ -502,64 +501,6 @@ 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
68 changes: 66 additions & 2 deletions address/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightningnetwork/lnd/keychain"
)

Expand All @@ -23,6 +24,10 @@ var (
// This means an address can't be created until a Universe bootstrap or
// manual issuance proof insertion.
ErrAssetGroupUnknown = fmt.Errorf("asset group is unknown")

// ErrAssetMetaNotFound is returned when an asset meta is not found in
// the database.
ErrAssetMetaNotFound = fmt.Errorf("asset meta not found")
)

// AddrWithKeyInfo wraps a normal Taproot Asset struct with key descriptor
Expand Down Expand Up @@ -100,6 +105,16 @@ type Storage interface {
// (genesis + group key) associated with a given asset.
QueryAssetGroup(context.Context, asset.ID) (*asset.AssetGroup, error)

// FetchAssetMetaByHash attempts to fetch an asset meta based on an
// asset hash.
FetchAssetMetaByHash(ctx context.Context,
metaHash [asset.MetaHashLen]byte) (*proof.MetaReveal, error)

// FetchAssetMetaForAsset attempts to fetch an asset meta based on an
// asset ID.
FetchAssetMetaForAsset(ctx context.Context,
assetID asset.ID) (*proof.MetaReveal, error)

// AddrByTaprootOutput returns a single address based on its Taproot
// output key or a sql.ErrNoRows error if no such address exists.
AddrByTaprootOutput(ctx context.Context,
Expand Down Expand Up @@ -218,7 +233,7 @@ func (b *Book) QueryAssetInfo(ctx context.Context,
return nil, err
}

log.Debugf("asset %v is unknown, attempting to bootstrap", id.String())
log.Debugf("Asset %v is unknown, attempting to bootstrap", id.String())

// Use the AssetSyncer to query our universe federation for the asset.
err = b.cfg.Syncer.SyncAssetInfo(ctx, &id)
Expand All @@ -233,7 +248,7 @@ func (b *Book) QueryAssetInfo(ctx context.Context,
return nil, err
}

log.Debugf("bootstrap succeeded for asset %v", id.String())
log.Debugf("Bootstrap succeeded for asset %v", id.String())

// If the asset was found after sync, and has an asset group, update our
// universe sync config to ensure that we sync future issuance proofs.
Expand All @@ -253,6 +268,55 @@ func (b *Book) QueryAssetInfo(ctx context.Context,
return assetGroup, nil
}

// FetchAssetMetaByHash attempts to fetch an asset meta based on an asset hash.
func (b *Book) FetchAssetMetaByHash(ctx context.Context,
metaHash [asset.MetaHashLen]byte) (*proof.MetaReveal, error) {

return b.cfg.Store.FetchAssetMetaByHash(ctx, metaHash)
}

// FetchAssetMetaForAsset attempts to fetch an asset meta based on an asset ID.
func (b *Book) FetchAssetMetaForAsset(ctx context.Context,
assetID asset.ID) (*proof.MetaReveal, error) {

// Check if we know of this meta hash already.
meta, err := b.cfg.Store.FetchAssetMetaForAsset(ctx, assetID)
switch {
case meta != nil:
return meta, nil

// Asset lookup failed gracefully; continue to asset lookup using the
// AssetSyncer if enabled.
case errors.Is(err, ErrAssetMetaNotFound):
if b.cfg.Syncer == nil {
return nil, ErrAssetMetaNotFound
}

case err != nil:
return nil, err
}

log.Debugf("Asset %v is unknown, attempting to bootstrap",
assetID.String())

// Use the AssetSyncer to query our universe federation for the asset.
err = b.cfg.Syncer.SyncAssetInfo(ctx, &assetID)
if err != nil {
return nil, err
}

// The asset meta info may have been synced from a universe server;
// query for the asset ID again.
meta, err = b.cfg.Store.FetchAssetMetaForAsset(ctx, assetID)
if err != nil {
return nil, err
}

log.Debugf("Bootstrap succeeded for asset %v", assetID.String())

return meta, nil
}

// NewAddress creates a new Taproot Asset address based on the input parameters.
func (b *Book) NewAddress(ctx context.Context, addrVersion Version,
assetID asset.ID, amount uint64,
Expand Down
51 changes: 50 additions & 1 deletion asset/witness.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package asset

import "github.com/btcsuite/btcd/btcec/v2"
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/taproot-assets/fn"
)

// IsSplitCommitWitness returns true if the witness is a split-commitment
// witness.
Expand Down Expand Up @@ -34,3 +38,48 @@ func IsBurnKey(scriptKey *btcec.PublicKey, witness Witness) bool {

return scriptKey.IsEqual(DeriveBurnKey(prevID))
}

// 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]) ScriptKey {
var (
nums, g, res btcec.JacobianPoint
challenge secp256k1.ModNScalar
)

if challengeBytesOpt.IsNone() {
return NUMSScriptKey
}

var challengeBytes [32]byte

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

// Convert the NUMS key to a Jacobian point.
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 NewScriptKey(resultPubKey)
}
66 changes: 66 additions & 0 deletions asset/witness_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package asset

import (
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/stretchr/testify/require"
)

// 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 ScriptKey
}{
{
name: "no challenge",
challenge: fn.None[[32]byte](),
expectedKey: 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: NewScriptKey(addG(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: NewScriptKey(addG(addG(NUMSPubKey))),
},
}

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