Skip to content

Commit 31eed72

Browse files
committed
asset: add Specifier.Equal method
To make sure we can compare two asset specifiers, we add a new Equal method that returns true if two specifiers are the same. The strictness can be chosen with the strict boolean argument. If it is set, then both specifiers need to either have both equal asset IDs and group keys set or only one of those (but matching). If strict is false, then it's enough to have the same group key _or_ the same asset ID (if one has no group key set).
1 parent 4d5b18f commit 31eed72

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

asset/asset.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,85 @@ func (s *Specifier) AssertNotEmpty() error {
604604
return nil
605605
}
606606

607+
// Equal compares this specifier to another one, returning true if they are
608+
// equal. If strict is true, then both specifiers need to either have both equal
609+
// asset IDs and group keys set or only one of those (but matching). If strict
610+
// is false, then it's enough to have the same group key _or_ the same asset ID
611+
// (if one has no group key set). This is useful for cases where one specifier
612+
// only specifies the group key, while the other one specifies both the group
613+
// key and the asset ID. In that case, we can consider them equal if the group
614+
// keys match, even if the asset IDs are different (or one is not set).
615+
func (s *Specifier) Equal(other *Specifier, strict bool) (bool, error) {
616+
// If both specifiers are nil, they are equal.
617+
if s == nil && other == nil {
618+
return true, nil
619+
}
620+
621+
// If one of the specifiers is nil, they are not equal.
622+
if s == nil || other == nil {
623+
return false, nil
624+
}
625+
626+
// If either of them is empty while the other is not, they are not
627+
// equal.
628+
if (s.HasId() || s.HasGroupPubKey()) !=
629+
(other.HasId() || other.HasGroupPubKey()) {
630+
631+
return false, nil
632+
}
633+
634+
// If they both have distinct elements set, then they are not equal.
635+
if s.HasId() != other.HasId() &&
636+
s.HasGroupPubKey() != other.HasGroupPubKey() {
637+
638+
return false, nil
639+
}
640+
641+
// If both specifiers have a group public key, compare them.
642+
if s.HasGroupPubKey() && other.HasGroupPubKey() {
643+
groupKeyA := s.UnwrapGroupKeyToPtr()
644+
groupKeyB := other.UnwrapGroupKeyToPtr()
645+
646+
// If any unwrapped element is nil, something's wrong, and we
647+
// can't compare them.
648+
if groupKeyA == nil || groupKeyB == nil {
649+
return false, fmt.Errorf("unable to unwrap group key "+
650+
"from specifier: %v vs %v", s, other)
651+
}
652+
653+
if !groupKeyA.IsEqual(groupKeyB) {
654+
return false, nil
655+
}
656+
657+
// If we're not doing a strict comparison, then we can return
658+
// true here if the group keys match. The group key has higher
659+
// priority than the ID, so if they match and the comparison
660+
// isn't strict, we can consider the specifiers equal.
661+
if !strict {
662+
return true, nil
663+
}
664+
}
665+
666+
// If both specifiers have an ID, compare them.
667+
if s.HasId() && other.HasId() {
668+
idA := s.UnwrapIdToPtr()
669+
idB := other.UnwrapIdToPtr()
670+
671+
// If any unwrapped element is nil, something's wrong and we
672+
// can't compare them.
673+
if idA == nil || idB == nil {
674+
return false, fmt.Errorf("unable to unwrap asset ID "+
675+
"from specifier: %v vs %v", s, other)
676+
}
677+
678+
if *idA != *idB {
679+
return false, nil
680+
}
681+
}
682+
683+
return true, nil
684+
}
685+
607686
// Type denotes the asset types supported by the Taproot Asset protocol.
608687
type Type uint8
609688

asset/asset_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,3 +1247,125 @@ func TestDecodeAsset(t *testing.T) {
12471247

12481248
t.Logf("Decoded asset: %v", string(assetJSON))
12491249
}
1250+
1251+
// TestSpecifierEqual tests the Specifier.Equal method for all possible cases.
1252+
func TestSpecifierEqual(t *testing.T) {
1253+
id1 := ID{1, 2, 3}
1254+
id2 := ID{4, 5, 6}
1255+
pk1, _ := btcec.NewPrivateKey()
1256+
pubKey1 := pk1.PubKey()
1257+
pk2, _ := btcec.NewPrivateKey()
1258+
pubKey2 := pk2.PubKey()
1259+
1260+
cases := []struct {
1261+
name string
1262+
s, other *Specifier
1263+
strict bool
1264+
expects bool
1265+
expectErr bool
1266+
}{
1267+
{
1268+
name: "both nil",
1269+
s: nil,
1270+
other: nil,
1271+
strict: false,
1272+
expects: true,
1273+
},
1274+
{
1275+
name: "one nil (s)",
1276+
s: nil,
1277+
other: &Specifier{},
1278+
strict: false,
1279+
expects: false,
1280+
},
1281+
{
1282+
name: "one nil (other)",
1283+
s: &Specifier{},
1284+
other: nil,
1285+
strict: false,
1286+
expects: false,
1287+
},
1288+
{
1289+
name: "both empty",
1290+
s: &Specifier{},
1291+
other: &Specifier{},
1292+
strict: false,
1293+
expects: true,
1294+
},
1295+
{
1296+
name: "both with same ID",
1297+
s: &Specifier{id: fn.Some(id1)},
1298+
other: &Specifier{id: fn.Some(id1)},
1299+
strict: false,
1300+
expects: true,
1301+
},
1302+
{
1303+
name: "both with different ID",
1304+
s: &Specifier{id: fn.Some(id1)},
1305+
other: &Specifier{id: fn.Some(id2)},
1306+
strict: false,
1307+
expects: false,
1308+
},
1309+
{
1310+
name: "both with same group key",
1311+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1312+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1313+
strict: false,
1314+
expects: true,
1315+
},
1316+
{
1317+
name: "both with different group key",
1318+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1319+
other: &Specifier{groupKey: fn.Some(*pubKey2)},
1320+
strict: false,
1321+
expects: false,
1322+
},
1323+
{
1324+
name: "one with ID, one with group key",
1325+
s: &Specifier{id: fn.Some(id1)},
1326+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1327+
strict: false,
1328+
expects: false,
1329+
},
1330+
{
1331+
name: "both with ID, strict true",
1332+
s: &Specifier{id: fn.Some(id1)},
1333+
other: &Specifier{id: fn.Some(id1)},
1334+
strict: true,
1335+
expects: true,
1336+
},
1337+
{
1338+
name: "both with group key, strict true",
1339+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1340+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1341+
strict: true,
1342+
expects: true,
1343+
},
1344+
{
1345+
name: "one with ID, one with group key, strict true",
1346+
s: &Specifier{id: fn.Some(id1)},
1347+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1348+
strict: true,
1349+
expects: false,
1350+
},
1351+
{
1352+
name: "both empty, strict true",
1353+
s: &Specifier{},
1354+
other: &Specifier{},
1355+
strict: true,
1356+
expects: true,
1357+
},
1358+
}
1359+
1360+
for _, tc := range cases {
1361+
t.Run(tc.name, func(t *testing.T) {
1362+
eq, err := tc.s.Equal(tc.other, tc.strict)
1363+
if tc.expectErr {
1364+
require.Error(t, err)
1365+
} else {
1366+
require.NoError(t, err)
1367+
require.Equal(t, tc.expects, eq)
1368+
}
1369+
})
1370+
}
1371+
}

0 commit comments

Comments
 (0)