tutus-chain/pkg/core/state/vita.go

481 lines
15 KiB
Go

package state
import (
"errors"
"fmt"
"math"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// TokenStatus represents the status of a Vita token.
type TokenStatus uint8
const (
// TokenStatusActive indicates an active token.
TokenStatusActive TokenStatus = 0
// TokenStatusSuspended indicates a temporarily suspended token.
TokenStatusSuspended TokenStatus = 1
// TokenStatusRevoked indicates a permanently revoked token.
TokenStatusRevoked TokenStatus = 2
// TokenStatusRecovering indicates recovery is in progress.
TokenStatusRecovering TokenStatus = 3
)
// DisclosureLevel represents the visibility level of an attribute.
type DisclosureLevel uint8
const (
// DisclosurePrivate means only the owner can see the attribute.
DisclosurePrivate DisclosureLevel = 0
// DisclosureVerifier means owner and designated verifiers can see it.
DisclosureVerifier DisclosureLevel = 1
// DisclosureRole means owner and callers with specified roles can see it.
DisclosureRole DisclosureLevel = 2
// DisclosurePublic means anyone can see the attribute.
DisclosurePublic DisclosureLevel = 3
)
// RecoveryStatus represents the status of a recovery request.
type RecoveryStatus uint8
const (
// RecoveryStatusPending indicates recovery is pending approval.
RecoveryStatusPending RecoveryStatus = 0
// RecoveryStatusApproved indicates recovery has been approved.
RecoveryStatusApproved RecoveryStatus = 1
// RecoveryStatusExecuted indicates recovery has been executed.
RecoveryStatusExecuted RecoveryStatus = 2
// RecoveryStatusDenied indicates recovery has been denied.
RecoveryStatusDenied RecoveryStatus = 3
// RecoveryStatusExpired indicates recovery request has expired.
RecoveryStatusExpired RecoveryStatus = 4
)
// Vita represents a soul-bound identity token.
type Vita struct {
TokenID uint64 // Unique sequential identifier
Owner util.Uint160 // Owner's script hash
PersonHash []byte // Hash of biometric/identity proof
IsEntity bool // True if organization, false if natural person
CreatedAt uint32 // Block height when created
UpdatedAt uint32 // Block height of last modification
Status TokenStatus // Current status
StatusReason string // Reason for status change
RecoveryHash []byte // Hash of recovery mechanism
VestedUntil uint32 // Block height until which the Vita is vesting (Sybil resistance)
}
// ToStackItem implements stackitem.Convertible interface.
func (t *Vita) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(t.TokenID))),
stackitem.NewByteArray(t.Owner.BytesBE()),
stackitem.NewByteArray(t.PersonHash),
stackitem.NewBool(t.IsEntity),
stackitem.NewBigInteger(big.NewInt(int64(t.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(t.UpdatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(t.Status))),
stackitem.NewByteArray([]byte(t.StatusReason)),
stackitem.NewByteArray(t.RecoveryHash),
stackitem.NewBigInteger(big.NewInt(int64(t.VestedUntil))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (t *Vita) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 10 {
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
}
tokenID, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid tokenID: %w", err)
}
t.TokenID = tokenID.Uint64()
ownerBytes, err := items[1].TryBytes()
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
t.Owner, err = util.Uint160DecodeBytesBE(ownerBytes)
if err != nil {
return fmt.Errorf("invalid owner hash: %w", err)
}
t.PersonHash, err = items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid personHash: %w", err)
}
isEntity, err := items[3].TryBool()
if err != nil {
return fmt.Errorf("invalid isEntity: %w", err)
}
t.IsEntity = isEntity
createdAt, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
t.CreatedAt = uint32(createdAt.Int64())
updatedAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid updatedAt: %w", err)
}
t.UpdatedAt = uint32(updatedAt.Int64())
status, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
t.Status = TokenStatus(status.Int64())
statusReasonBytes, err := items[7].TryBytes()
if err != nil {
return fmt.Errorf("invalid statusReason: %w", err)
}
t.StatusReason = string(statusReasonBytes)
t.RecoveryHash, err = items[8].TryBytes()
if err != nil {
return fmt.Errorf("invalid recoveryHash: %w", err)
}
vestedUntil, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid vestedUntil: %w", err)
}
t.VestedUntil = uint32(vestedUntil.Int64())
return nil
}
// Attribute represents an identity attribute with disclosure control.
type Attribute struct {
Key string // Attribute name
ValueHash []byte // Hash of value (privacy)
ValueEnc []byte // Encrypted value (optional)
Attestor util.Uint160 // Who attested this (script hash)
AttestedAt uint32 // Block height when attested
ExpiresAt uint32 // Optional expiration (0 = never)
Revoked bool // Whether attribute has been revoked
RevokedAt uint32 // Block height when revoked
DisclosureLevel DisclosureLevel // Visibility level
}
// ToStackItem implements stackitem.Convertible interface.
func (a *Attribute) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray([]byte(a.Key)),
stackitem.NewByteArray(a.ValueHash),
stackitem.NewByteArray(a.ValueEnc),
stackitem.NewByteArray(a.Attestor.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(a.AttestedAt))),
stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
stackitem.NewBool(a.Revoked),
stackitem.NewBigInteger(big.NewInt(int64(a.RevokedAt))),
stackitem.NewBigInteger(big.NewInt(int64(a.DisclosureLevel))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (a *Attribute) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 9 {
return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
}
keyBytes, err := items[0].TryBytes()
if err != nil {
return fmt.Errorf("invalid key: %w", err)
}
a.Key = string(keyBytes)
a.ValueHash, err = items[1].TryBytes()
if err != nil {
return fmt.Errorf("invalid valueHash: %w", err)
}
a.ValueEnc, err = items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid valueEnc: %w", err)
}
attestorBytes, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid attestor: %w", err)
}
a.Attestor, err = util.Uint160DecodeBytesBE(attestorBytes)
if err != nil {
return fmt.Errorf("invalid attestor hash: %w", err)
}
attestedAt, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid attestedAt: %w", err)
}
a.AttestedAt = uint32(attestedAt.Int64())
expiresAt, err := items[5].TryInteger()
if err != nil {
return fmt.Errorf("invalid expiresAt: %w", err)
}
a.ExpiresAt = uint32(expiresAt.Int64())
a.Revoked, err = items[6].TryBool()
if err != nil {
return fmt.Errorf("invalid revoked: %w", err)
}
revokedAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid revokedAt: %w", err)
}
a.RevokedAt = uint32(revokedAt.Int64())
disclosureLevel, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid disclosureLevel: %w", err)
}
a.DisclosureLevel = DisclosureLevel(disclosureLevel.Int64())
return nil
}
// AuthChallenge represents a passwordless authentication challenge.
type AuthChallenge struct {
ChallengeID util.Uint256 // Unique challenge identifier
TokenID uint64 // Associated Vita token
Nonce []byte // Random bytes to sign
CreatedAt uint32 // Block height when created
ExpiresAt uint32 // Block height when expires
Purpose string // "login", "sign", "recover"
Fulfilled bool // Whether challenge has been fulfilled
FulfilledAt uint32 // Block height when fulfilled
}
// ToStackItem implements stackitem.Convertible interface.
func (c *AuthChallenge) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray(c.ChallengeID.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(c.TokenID))),
stackitem.NewByteArray(c.Nonce),
stackitem.NewBigInteger(big.NewInt(int64(c.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))),
stackitem.NewByteArray([]byte(c.Purpose)),
stackitem.NewBool(c.Fulfilled),
stackitem.NewBigInteger(big.NewInt(int64(c.FulfilledAt))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (c *AuthChallenge) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 8 {
return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
}
challengeIDBytes, err := items[0].TryBytes()
if err != nil {
return fmt.Errorf("invalid challengeID: %w", err)
}
c.ChallengeID, err = util.Uint256DecodeBytesBE(challengeIDBytes)
if err != nil {
return fmt.Errorf("invalid challengeID hash: %w", err)
}
tokenID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid tokenID: %w", err)
}
c.TokenID = tokenID.Uint64()
c.Nonce, err = items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid nonce: %w", err)
}
createdAt, err := items[3].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
c.CreatedAt = uint32(createdAt.Int64())
expiresAt, err := items[4].TryInteger()
if err != nil {
return fmt.Errorf("invalid expiresAt: %w", err)
}
c.ExpiresAt = uint32(expiresAt.Int64())
purposeBytes, err := items[5].TryBytes()
if err != nil {
return fmt.Errorf("invalid purpose: %w", err)
}
c.Purpose = string(purposeBytes)
c.Fulfilled, err = items[6].TryBool()
if err != nil {
return fmt.Errorf("invalid fulfilled: %w", err)
}
fulfilledAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid fulfilledAt: %w", err)
}
c.FulfilledAt = uint32(fulfilledAt.Int64())
return nil
}
// RecoveryRequest represents a key recovery request.
type RecoveryRequest struct {
RequestID util.Uint256 // Unique request identifier
TokenID uint64 // Token being recovered
NewOwner util.Uint160 // Proposed new owner
Requester util.Uint160 // Who initiated recovery
Evidence []byte // Encrypted evidence hash
Approvals []util.Uint160 // Approvers who have approved
RequiredApprovals int // Required number of approvals
CreatedAt uint32 // Block height when created
DelayUntil uint32 // Block height when executable
ExpiresAt uint32 // Block height when expires
Status RecoveryStatus // Current status
}
// ToStackItem implements stackitem.Convertible interface.
func (r *RecoveryRequest) ToStackItem() (stackitem.Item, error) {
approvals := make([]stackitem.Item, len(r.Approvals))
for i, a := range r.Approvals {
approvals[i] = stackitem.NewByteArray(a.BytesBE())
}
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray(r.RequestID.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(r.TokenID))),
stackitem.NewByteArray(r.NewOwner.BytesBE()),
stackitem.NewByteArray(r.Requester.BytesBE()),
stackitem.NewByteArray(r.Evidence),
stackitem.NewArray(approvals),
stackitem.NewBigInteger(big.NewInt(int64(r.RequiredApprovals))),
stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
stackitem.NewBigInteger(big.NewInt(int64(r.DelayUntil))),
stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))),
stackitem.NewBigInteger(big.NewInt(int64(r.Status))),
}), nil
}
// FromStackItem implements stackitem.Convertible interface.
func (r *RecoveryRequest) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) != 11 {
return fmt.Errorf("wrong number of elements: expected 11, got %d", len(items))
}
requestIDBytes, err := items[0].TryBytes()
if err != nil {
return fmt.Errorf("invalid requestID: %w", err)
}
r.RequestID, err = util.Uint256DecodeBytesBE(requestIDBytes)
if err != nil {
return fmt.Errorf("invalid requestID hash: %w", err)
}
tokenID, err := items[1].TryInteger()
if err != nil {
return fmt.Errorf("invalid tokenID: %w", err)
}
r.TokenID = tokenID.Uint64()
newOwnerBytes, err := items[2].TryBytes()
if err != nil {
return fmt.Errorf("invalid newOwner: %w", err)
}
r.NewOwner, err = util.Uint160DecodeBytesBE(newOwnerBytes)
if err != nil {
return fmt.Errorf("invalid newOwner hash: %w", err)
}
requesterBytes, err := items[3].TryBytes()
if err != nil {
return fmt.Errorf("invalid requester: %w", err)
}
r.Requester, err = util.Uint160DecodeBytesBE(requesterBytes)
if err != nil {
return fmt.Errorf("invalid requester hash: %w", err)
}
r.Evidence, err = items[4].TryBytes()
if err != nil {
return fmt.Errorf("invalid evidence: %w", err)
}
approvalsArray, ok := items[5].Value().([]stackitem.Item)
if !ok {
return errors.New("approvals is not an array")
}
r.Approvals = make([]util.Uint160, len(approvalsArray))
for i, a := range approvalsArray {
approvalBytes, err := a.TryBytes()
if err != nil {
return fmt.Errorf("invalid approval %d: %w", i, err)
}
r.Approvals[i], err = util.Uint160DecodeBytesBE(approvalBytes)
if err != nil {
return fmt.Errorf("invalid approval %d hash: %w", i, err)
}
}
requiredApprovals, err := items[6].TryInteger()
if err != nil {
return fmt.Errorf("invalid requiredApprovals: %w", err)
}
ra := requiredApprovals.Int64()
if ra < 0 || ra > math.MaxInt {
return errors.New("requiredApprovals out of range")
}
r.RequiredApprovals = int(ra)
createdAt, err := items[7].TryInteger()
if err != nil {
return fmt.Errorf("invalid createdAt: %w", err)
}
r.CreatedAt = uint32(createdAt.Int64())
delayUntil, err := items[8].TryInteger()
if err != nil {
return fmt.Errorf("invalid delayUntil: %w", err)
}
r.DelayUntil = uint32(delayUntil.Int64())
expiresAt, err := items[9].TryInteger()
if err != nil {
return fmt.Errorf("invalid expiresAt: %w", err)
}
r.ExpiresAt = uint32(expiresAt.Int64())
status, err := items[10].TryInteger()
if err != nil {
return fmt.Errorf("invalid status: %w", err)
}
r.Status = RecoveryStatus(status.Int64())
return nil
}