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 PersonToken. 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 ) // PersonToken represents a soul-bound identity token. type PersonToken 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 } // ToStackItem implements stackitem.Convertible interface. func (t *PersonToken) 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), }), nil } // FromStackItem implements stackitem.Convertible interface. func (t *PersonToken) 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)) } 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) } 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 PersonToken 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 }