go-lns/pkg/covenant/names.go
2026-04-04 07:58:52 +00:00

365 lines
8 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package covenant
import "dappco.re/go/lns/pkg/primitives"
func nameHashFromCovenant(c primitives.Covenant) (primitives.Hash, bool) {
hash, err := c.GetHash(0)
if err != nil {
return primitives.Hash{}, false
}
return hash, true
}
func nameSetHas(set map[primitives.Hash]struct{}, hash primitives.Hash) bool {
if set == nil {
return false
}
_, ok := set[hash]
return ok
}
func nameSetAdd(set map[primitives.Hash]struct{}, hash primitives.Hash) {
if set == nil {
return
}
set[hash] = struct{}{}
}
func nameSetDelete(set map[primitives.Hash]struct{}, hash primitives.Hash) {
if set == nil {
return
}
delete(set, hash)
}
// HasNames reports whether any name covenant in the transaction references a
// hash that is already present in the set.
func HasNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) bool {
for _, output := range tx.Outputs {
if !output.Covenant.IsName() {
continue
}
hash, ok := nameHashFromCovenant(output.Covenant)
if !ok {
continue
}
switch CovenantType(output.Covenant.Type) {
case TypeClaim, TypeOpen, TypeRegister, TypeUpdate, TypeRenew, TypeTransfer, TypeFinalize, TypeRevoke:
if nameSetHas(set, hash) {
return true
}
}
}
return false
}
// GetHasNames is an alias for HasNames.
//
// ok := covenant.GetHasNames(tx, set)
func GetHasNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) bool {
return HasNames(tx, set)
}
// AddNames adds the transaction's name covenants to the set.
func AddNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) {
for _, output := range tx.Outputs {
if !output.Covenant.IsName() {
continue
}
hash, ok := nameHashFromCovenant(output.Covenant)
if !ok {
continue
}
switch CovenantType(output.Covenant.Type) {
case TypeClaim, TypeOpen, TypeRegister, TypeUpdate, TypeRenew, TypeTransfer, TypeFinalize, TypeRevoke:
nameSetAdd(set, hash)
}
}
}
// GetAddNames is an alias for AddNames.
//
// covenant.GetAddNames(tx, set)
func GetAddNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) {
AddNames(tx, set)
}
// RemoveNames removes the transaction's name covenants from the set.
func RemoveNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) {
for _, output := range tx.Outputs {
if !output.Covenant.IsName() {
continue
}
hash, ok := nameHashFromCovenant(output.Covenant)
if !ok {
continue
}
switch CovenantType(output.Covenant.Type) {
case TypeClaim, TypeOpen, TypeRegister, TypeUpdate, TypeRenew, TypeTransfer, TypeFinalize, TypeRevoke:
nameSetDelete(set, hash)
}
}
}
// GetRemoveNames is an alias for RemoveNames.
//
// covenant.GetRemoveNames(tx, set)
func GetRemoveNames(tx primitives.Transaction, set map[primitives.Hash]struct{}) {
RemoveNames(tx, set)
}
func isCoinbaseTx(tx primitives.Transaction) bool {
return len(tx.Inputs) > 0 && tx.Inputs[0].IsCoinbase()
}
func validNameHashAndLabel(c primitives.Covenant, hashIndex, nameIndex int) bool {
hash, err := c.GetHash(hashIndex)
if err != nil {
return false
}
name, err := c.GetString(nameIndex)
if err != nil {
return false
}
key, err := HashString(name)
if err != nil {
return false
}
return key == hash
}
// HasSaneCovenants performs structural covenant validation on a transaction.
//
// The helper mirrors the JS rules.js sanity checks that only depend on the
// transaction contents. Contextual witness and proof validation is handled by
// higher-level consensus logic.
func HasSaneCovenants(tx primitives.Transaction) bool {
if isCoinbaseTx(tx) {
if len(tx.Inputs) > len(tx.Outputs) {
return false
}
for i, output := range tx.Outputs {
cov := output.Covenant
switch CovenantType(cov.Type) {
case TypeNone:
if cov.Len() != 0 {
return false
}
if i > 0 && i < len(tx.Inputs) {
input := tx.Inputs[i]
if len(input.Witness) != 1 {
return false
}
proof, err := decodeAirdropProof(input.Witness[0])
if err != nil || !proof.isSane() {
return false
}
}
case TypeClaim:
if i == 0 || i >= len(tx.Inputs) {
return false
}
if cov.Len() != 6 {
return false
}
input := tx.Inputs[i]
if len(input.Witness) != 1 {
return false
}
if len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) == 0 || len(cov.Items[2]) > MaxNameSize || len(cov.Items[3]) != 1 || len(cov.Items[4]) != 32 || len(cov.Items[5]) != 4 {
return false
}
if !VerifyString(string(cov.Items[2])) {
return false
}
var claim primitives.Claim
if err := claim.UnmarshalBinary(input.Witness[0]); err != nil {
return false
}
var hash primitives.Hash
copy(hash[:], cov.Items[0])
if !HasReservedHash(hash) {
return false
}
if !validNameHashAndLabel(cov, 0, 2) {
return false
}
default:
return false
}
}
return true
}
for i, output := range tx.Outputs {
cov := output.Covenant
switch CovenantType(cov.Type) {
case TypeNone:
if cov.Len() != 0 {
return false
}
case TypeClaim:
return false
case TypeOpen:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 3 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) == 0 || len(cov.Items[2]) > MaxNameSize {
return false
}
if !VerifyString(string(cov.Items[2])) || !validNameHashAndLabel(cov, 0, 2) {
return false
}
if h, err := cov.GetU32(1); err != nil || h != 0 {
return false
}
case TypeBid:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 4 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) == 0 || len(cov.Items[2]) > MaxNameSize || len(cov.Items[3]) != 32 {
return false
}
if !VerifyString(string(cov.Items[2])) || !validNameHashAndLabel(cov, 0, 2) {
return false
}
case TypeReveal:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 3 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) != 32 {
return false
}
case TypeRedeem:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 2 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 {
return false
}
case TypeRegister:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 4 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) > MaxResourceSize || len(cov.Items[3]) != 32 {
return false
}
case TypeUpdate:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 3 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) > MaxResourceSize {
return false
}
case TypeRenew:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 3 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) != 32 {
return false
}
case TypeTransfer:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 4 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) != 1 {
return false
}
version := cov.Items[2][0]
hash := cov.Items[3]
if version > 31 || len(hash) < 2 || len(hash) > 40 {
return false
}
case TypeFinalize:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 7 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 || len(cov.Items[2]) == 0 || len(cov.Items[2]) > MaxNameSize || len(cov.Items[3]) != 1 || len(cov.Items[4]) != 4 || len(cov.Items[5]) != 4 || len(cov.Items[6]) != 32 {
return false
}
if !VerifyString(string(cov.Items[2])) || !validNameHashAndLabel(cov, 0, 2) {
return false
}
case TypeRevoke:
if i >= len(tx.Inputs) {
return false
}
if cov.Len() != 2 || len(cov.Items[0]) != 32 || len(cov.Items[1]) != 4 {
return false
}
default:
if cov.Len() > maxScriptStack || cov.GetSize() > MaxCovenantSize {
return false
}
}
}
return true
}
// GetHasSaneCovenants is an alias for HasSaneCovenants.
//
// ok := covenant.GetHasSaneCovenants(tx)
func GetHasSaneCovenants(tx primitives.Transaction) bool {
return HasSaneCovenants(tx)
}