package token
import (
"bytes"
"encoding/binary"
"encoding/hex"
"math/big"
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/pkg/errors"
hg "source.quilibrium.com/quilibrium/monorepo/node/execution/state/hypergraph"
"source.quilibrium.com/quilibrium/monorepo/protobufs"
"source.quilibrium.com/quilibrium/monorepo/types/crypto"
"source.quilibrium.com/quilibrium/monorepo/types/schema"
qcrypto "source.quilibrium.com/quilibrium/monorepo/types/tries"
)
var QUIL_TOKEN_ADDRESS = []byte{
// poseidon("q_mainnet_token")
0x11, 0x55, 0x85, 0x84, 0xaf, 0x70, 0x17, 0xa9,
0xbf, 0xd1, 0xff, 0x18, 0x64, 0x30, 0x2d, 0x64,
0x3f, 0xbe, 0x58, 0xc6, 0x2d, 0xcf, 0x90, 0xcb,
0xcd, 0x8f, 0xde, 0x74, 0xa2, 0x67, 0x94, 0xd9,
}
var QUIL_TOKEN_CONFIGURATION = &TokenIntrinsicConfiguration{
Behavior: Mintable | Burnable | Divisible | Acceptable | Expirable | Tenderable,
MintStrategy: &TokenMintStrategy{
MintBehavior: MintWithProof,
ProofBasis: ProofOfMeaningfulWork,
},
Units: big.NewInt(8000000000),
Name: "QUIL",
Symbol: "QUIL",
}
var TOKEN_PREFIX = []byte("q_token")
var TOKEN_SUPPLY = []byte("q_token_current_supply")
var TOKEN_AVAILABLE_REFERENCES = []byte("q_token_additional_references")
var TOKEN_BASE_DOMAIN [32]byte
var TOKEN_SUPPLY_ADDRESS [32]byte
var TOKEN_ADDITIONAL_REFRENCES_ADDRESS [32]byte
var TOKEN_CONFIGURATION_METADATA_SCHEMA = `BASE
PREFIX rdf:
PREFIX rdfs:
PREFIX qcl:
PREFIX config:
config:TokenConfiguration a rdfs:Class.
config:Behavior a rdfs:Property;
rdfs:domain qcl:Uint;
qcl:size 2;
qcl:order 0;
rdfs:range config:TokenConfiguration.
config:MintStrategy a rdfs:Property;
rdfs:domain qcl:ByteArray;
qcl:size 701;
qcl:order 1;
rdfs:range config:TokenConfiguration.
config:Units a rdfs:Property;
rdfs:domain qcl:ByteArray;
qcl:size 32;
qcl:order 2;
rdfs:range config:TokenConfiguration.
config:Supply a rdfs:Property;
rdfs:domain qcl:ByteArray;
qcl:size 32;
qcl:order 3;
rdfs:range config:TokenConfiguration.
config:Name a rdfs:Property;
rdfs:domain qcl:String;
qcl:size 64;
qcl:order 4;
rdfs:range config:TokenConfiguration.
config:Symbol a rdfs:Property;
rdfs:domain qcl:String;
qcl:size 8;
qcl:order 5;
rdfs:range config:TokenConfiguration.
config:AdditionalReference a rdfs:Property;
rdfs:domain qcl:ByteArray;
qcl:size 64;
qcl:order 6;
rdfs:range config:TokenConfiguration.
config:OwnerPublicKey a rdfs:Property;
rdfs:domain qcl:ByteArray;
qcl:size 585;
qcl:order 7;
rdfs:range config:TokenConfiguration.
`
func init() {
tokenDomainBI, err := poseidon.HashBytes(TOKEN_PREFIX)
if err != nil {
panic(err)
}
TOKEN_BASE_DOMAIN = [32]byte(tokenDomainBI.FillBytes(make([]byte, 32)))
tokenSupplyBI, err := poseidon.HashBytes(TOKEN_SUPPLY)
if err != nil {
panic(err)
}
// Set supply address out of field modulus for poseidon to prevent collision
TOKEN_SUPPLY_ADDRESS = [32]byte(tokenSupplyBI.FillBytes(make([]byte, 32)))
TOKEN_SUPPLY_ADDRESS[0] = 0xff
tokenAdditionalReferencesBI, err := poseidon.HashBytes(
TOKEN_AVAILABLE_REFERENCES,
)
if err != nil {
panic(err)
}
// Set additional reference address out of field modulus for poseidon to
// prevent collision
TOKEN_ADDITIONAL_REFRENCES_ADDRESS = [32]byte(
tokenAdditionalReferencesBI.FillBytes(make([]byte, 32)),
)
TOKEN_ADDITIONAL_REFRENCES_ADDRESS[0] = 0xff
}
type TokenIntrinsicBehavior uint16
const (
// Has an explicit mint authority - If Mintable is set, MintStrategy MUST be
// defined, and Supply MAY be set. If not set, Supply MUST be set, and the
// total supply is minted to the creator.
Mintable TokenIntrinsicBehavior = 1 << iota
// Allows the token to be burnt – If Burnable is set, Burn will decrease
// Supply on Burn events. Additional behaviors apply based on MintStrategy.
Burnable
// Can be merged/split - If Divisible is set, Units MUST be defined. If not
// set, Units MUST NOT be defined.
Divisible
// Enables pending transaction flow – If Acceptable is set, transaction flow
// is Transfer -> PendingTransaction, Accept -> Transaction, Reject ->
// PendingTransaction, MutualTransfer -> Transaction. If Acceptable is not
// set, transaction flow is Transfer -> Transaction.
Acceptable
// Enables expirations on pending transactions – If Expirable is set,
// Acceptable MUST be set, uses Deadline field of PendingTransaction to permit
// RefundAddress to issue an Accept.
Expirable
// Permits application shards to set their fee basis in denomination of the
// token. Important note: configuring an application shard to do this is
// dangerous – the only consensus maintained by the network natively is its
// root commitment, the shard will have no impact on QUIL emissions and may
// go offline if all nodes configured to cover it also go offline. If
// Tenderable is set, and MintStrategy is configured to use MintWithProof,
// the nodes covering the application shard will be eligible to earn rewards
// denominated in the token, following the configured WorkBasis, otherwise
// there are no emissions-based rewards for covering that application shard,
// only fees.
Tenderable
)
type TokenMintBehavior uint16
const (
// Token is not mintable. No other values for MintStrategy may be provided.
NoMintBehavior TokenMintBehavior = 0
// Token is mintable given some ProofBasis – If MintWithProof is set,
// ProofBasis MUST be defined. If not set, ProofBasis MUST NOT be defined.
MintWithProof = 1 << 0
// Token is mintable only by an authority – If MintWithAuthority is set,
// Authority MUST be defined.
MintWithAuthority = 1 << 1
// Token is mintable with a signature from an authority – If MintWithSignature
// is set, Authority MUST be defined.
MintWithSignature = 1 << 2
// Token is mintable in exchange for a payment – If MintWithPayment is set,
// PaymentAddress MUST be defined and FeeBasis MUST be defined.
MintWithPayment = 1 << 3
)
type ProofBasisType uint16
const (
NoProofBasis ProofBasisType = iota
ProofOfMeaningfulWork
VerkleMultiproofWithSignature
)
type FeeBasisType uint16
const (
NoFeeBasis FeeBasisType = iota
PerUnit
)
type Authority struct {
KeyType crypto.KeyType
PublicKey []byte
CanBurn bool
}
type FeeBasis struct {
Type FeeBasisType
Baseline *big.Int
}
type TokenMintStrategy struct {
// Defines the mint behavior. For serialization purposes, an undefined
// MintStrategy will serialize MintBehavior as NoMintBehavior. For any
// other configurations of these values, MintBehavior MUST be defined as
// something other than NoMintBehavior.
MintBehavior TokenMintBehavior
// If MintWithProof is set, ProofBasis MUST be set to a value other than
// NoProofBasis.
ProofBasis ProofBasisType
// If ProofBasis is VerkleMultiproofWithSignature, this is the root commitment
// value. Otherwise, MUST be empty.
VerkleRoot []byte
// If MintWithAuthority or MintWithSignature is set, Authority MUST also be
// set.
Authority *Authority
// If MintWithPayment is set, PaymentAddress MUST be set.
PaymentAddress []byte
// If MintWithPayment is set, FeeBasis MUST be set, but MAY be zero.
FeeBasis *FeeBasis
}
type TokenIntrinsicConfiguration struct {
// Defines the behavior of the given token intrinsic in terms of operations
// that can be performed on instances of it.
Behavior TokenIntrinsicBehavior
// If Mintable is set, this MUST be defined
MintStrategy *TokenMintStrategy
// Divisible units of a token. If Divisible is NOT set, this MUST be undefined
// and will be interpreted as 1. Units MAY NOT be less than 1. Will be
// interpreted as the number of discrete units that makes a single whole
// instance of a token. Example: Most national currencies are divisible by
// 100, and so Units would be 100.
Units *big.Int
// Sets a total supply. If Mintable is NOT set, this MUST be defined. If not
// set, this will be interpreted as 2^255. This supply is in terms of Units,
// not an undivided whole. Example: A token with a divisibility of 100 units
// and a maximum supply of 100,000,000.00 tokens would be encoded as
// 10000000000.
Supply *big.Int
// The printable name of the token.
Name string
// The short-form name of the token.
Symbol string
// The address corresponding to additional informational records
AdditionalReference [64]byte
// The owner's public key (585 bytes for BLS48-581)
OwnerPublicKey []byte
}
// TokenDeploy creates a new token instance
type TokenDeploy struct {
// The token configuration
Config *TokenIntrinsicConfiguration
// The raw RDF schema definition
RDFSchema []byte
}
// TokenUpdate updates an existing token instance
type TokenUpdate struct {
// The token configuration
Config *TokenIntrinsicConfiguration
// The raw RDF schema definition
RDFSchema []byte
// Signature from the owner key
OwnerSignature *protobufs.BLS48581AggregateSignature
}
func newTokenConsensusMetadata(
provers [][]byte,
) (*qcrypto.VectorCommitmentTree, error) {
if len(provers) != 0 {
return nil, errors.Wrap(
errors.New(
"token intrinsic may not accept a prover list for initialization",
),
"new token consensus metadata",
)
}
return &qcrypto.VectorCommitmentTree{}, nil
}
func newTokenSumcheckInfo() (*qcrypto.VectorCommitmentTree, error) {
return &qcrypto.VectorCommitmentTree{}, nil
}
func GenerateRDFPrelude(
appAddress []byte,
config *TokenIntrinsicConfiguration,
) string {
appAddressHex := hex.EncodeToString(appAddress)
prelude := "BASE \n" +
"PREFIX rdf: \n" +
"PREFIX rdfs: \n" +
"PREFIX qcl: \n" +
"PREFIX coin: \n"
if config.Behavior&Acceptable != 0 {
prelude += "PREFIX pending: \n"
}
prelude += "\n"
return prelude
}
func PrepareRDFSchemaFromConfig(
appAddress []byte,
config *TokenIntrinsicConfiguration,
) (string, error) {
schema := GenerateRDFPrelude(appAddress, config)
schema += "coin:Coin a rdfs:Class.\n" +
"coin:FrameNumber a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 8;\n" +
" qcl:order 0;\n" +
" rdfs:range coin:Coin.\n" +
"coin:Commitment a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 1;\n" +
" rdfs:range coin:Coin.\n" +
"coin:OneTimeKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 2;\n" +
" rdfs:range coin:Coin.\n" +
"coin:VerificationKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 3;\n" +
" rdfs:range coin:Coin.\n" +
"coin:CoinBalance a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 56;\n" +
" qcl:order 4;\n" +
" rdfs:range coin:Coin.\n" +
"coin:Mask a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 5;\n" +
" rdfs:range coin:Coin.\n"
if config.Behavior&Divisible == 0 {
schema += "coin:AdditionalReference a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 64;\n" +
" qcl:order 6;\n" +
" rdfs:range coin:Coin.\n"
schema += "coin:AdditionalReferenceKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 7;\n" +
" rdfs:range coin:Coin.\n"
}
if config.Behavior&Acceptable != 0 {
schema += "\npending:PendingTransaction a rdfs:Class;\n" +
" rdfs:label \"a pending transaction\".\n" +
"pending:FrameNumber a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 8;\n" +
" qcl:order 0;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:Commitment a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 1;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:ToOneTimeKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 2;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundOneTimeKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 3;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:ToVerificationKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 4;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundVerificationKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 5;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:ToCoinBalance a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 56;\n" +
" qcl:order 6;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundCoinBalance a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 56;\n" +
" qcl:order 7;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:ToMask a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 8;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundMask a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 9;\n" +
" rdfs:range pending:PendingTransaction.\n"
if config.Behavior&Divisible == 0 {
schema += "pending:ToAdditionalReference a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 64;\n" +
" qcl:order 10;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:ToAdditionalReferenceKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 11;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundAdditionalReference a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 64;\n" +
" qcl:order 12;\n" +
" rdfs:range pending:PendingTransaction.\n" +
"pending:RefundAdditionalReferenceKey a rdfs:Property;\n" +
" rdfs:domain qcl:ByteArray;\n" +
" qcl:size 56;\n" +
" qcl:order 13;\n" +
" rdfs:range pending:PendingTransaction.\n"
}
if config.Behavior&Expirable != 0 {
schema += "pending:Expiration a rdfs:Property;\n" +
" rdfs:domain qcl:Uint;\n" +
" qcl:size 8;\n"
if config.Behavior&Divisible == 0 {
schema += " qcl:order 14;\n"
} else {
schema += " qcl:order 10;\n"
}
schema += " rdfs:range pending:PendingTransaction.\n"
}
}
schema += "\n"
return schema, nil
}
func newTokenRDFHypergraphSchema(
appAddress []byte,
config *TokenIntrinsicConfiguration,
) (string, error) {
schemaDoc, err := PrepareRDFSchemaFromConfig(appAddress, config)
if err != nil {
return "", errors.Wrap(err, "new token rdf hypergraph schema")
}
valid, err := (&schema.TurtleRDFParser{}).Validate(schemaDoc)
if err != nil {
return "", errors.Wrap(err, "new token rdf hypergraph schema")
}
if !valid {
return "", errors.Wrap(
errors.New("invalid schema"),
"new token rdf hypergraph schema",
)
}
return schemaDoc, nil
}
func validateTokenConfiguration(config *TokenIntrinsicConfiguration) error {
// Verify config is valid based on behavior
if (config.Behavior&Mintable) == 0 && (config.Supply == nil ||
config.Supply.Cmp(big.NewInt(0)) <= 0 ||
config.Supply.Cmp(
big.NewInt(1).Lsh(big.NewInt(1), 256),
) >= 0) {
return errors.Wrap(
errors.New("non-mintable token must have supply defined"),
"validate token configuration",
)
}
if (config.Behavior&Mintable) != 0 && config.MintStrategy == nil {
return errors.Wrap(
errors.New("mintable token must have mint strategy defined"),
"validate token configuration",
)
}
if (config.Behavior&Divisible) != 0 && (config.Units == nil ||
config.Units.Cmp(big.NewInt(0)) <= 0 ||
config.Units.Cmp(
big.NewInt(1).Lsh(big.NewInt(1), 256),
) >= 0) {
return errors.Wrap(
errors.New("divisible token must have units defined"),
"validate token configuration",
)
}
if (config.Behavior&Divisible) == 0 && config.Units != nil {
return errors.Wrap(
errors.New("non-divisible token must not have units defined"),
"validate token configuration",
)
}
if (config.Behavior&Expirable) != 0 && (config.Behavior&Acceptable) == 0 {
return errors.Wrap(
errors.New("expirable token must be acceptable"),
"validate token configuration",
)
}
// Validate MintStrategy if present
if config.MintStrategy != nil {
switch config.MintStrategy.MintBehavior {
case NoMintBehavior:
// Nothing else should be defined
if config.MintStrategy.ProofBasis != NoProofBasis {
return errors.Wrap(
errors.New("no mint behavior must not define proof basis"),
"validate token configuration",
)
}
if config.MintStrategy.Authority != nil {
return errors.Wrap(
errors.New("no mint behavior must not define authority"),
"validate token configuration",
)
}
if len(config.MintStrategy.PaymentAddress) != 32 {
return errors.Wrap(
errors.New("no mint behavior must not define payment address"),
"validate token configuration",
)
}
if config.MintStrategy.FeeBasis != nil {
return errors.Wrap(
errors.New("no mint behavior must not define fee basis"),
"validate token configuration",
)
}
case MintWithProof:
if config.MintStrategy.ProofBasis == NoProofBasis {
return errors.Wrap(
errors.New("mint with proof must define proof basis"),
"validate token configuration",
)
}
if config.MintStrategy.ProofBasis == VerkleMultiproofWithSignature &&
len(config.MintStrategy.VerkleRoot) != 74 {
return errors.Wrap(
errors.New("verkle root must be defined"),
"validate token configuration",
)
}
case MintWithAuthority, MintWithSignature:
if config.MintStrategy.Authority == nil {
return errors.Wrap(
errors.New(
"mint with authority/signature must define authority",
),
"validate token configuration",
)
}
case MintWithPayment:
if len(config.MintStrategy.PaymentAddress) != 32 {
return errors.Wrap(
errors.New("mint with payment must define payment address"),
"validate token configuration",
)
}
if config.MintStrategy.FeeBasis == nil ||
config.MintStrategy.FeeBasis.Baseline.Cmp(big.NewInt(0)) < 0 ||
config.MintStrategy.FeeBasis.Baseline.Cmp(
big.NewInt(1).Lsh(big.NewInt(1), 256),
) >= 0 {
return errors.Wrap(
errors.New("mint with payment must define fee basis"),
"validate token configuration",
)
}
}
}
return nil
}
func NewTokenConfigurationMetadata(
config *TokenIntrinsicConfiguration,
rdfMultiprover *schema.RDFMultiprover,
) (*qcrypto.VectorCommitmentTree, error) {
if err := validateTokenConfiguration(config); err != nil {
return nil, errors.Wrap(err, "token config")
}
tree := &qcrypto.VectorCommitmentTree{}
// Store Behavior (order 0)
behaviorBytes := make([]byte, 2)
binary.BigEndian.PutUint16(behaviorBytes, uint16(config.Behavior))
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"Behavior",
behaviorBytes,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Store MintStrategy (order 1)
if config.MintStrategy != nil {
mintStrategyBytes := bytes.Buffer{}
// Write MintBehavior
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(config.MintStrategy.MintBehavior),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write ProofBasis
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(config.MintStrategy.ProofBasis),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write VerkleRoot if present
if config.MintStrategy.VerkleRoot != nil {
// Write 1 to indicate VerkleRoot is present
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(1),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write VerkleRoot length and bytes
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(len(config.MintStrategy.VerkleRoot)),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
if _, err := mintStrategyBytes.Write(
config.MintStrategy.VerkleRoot,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
} else {
// Write 0 to indicate no VerkleRoot
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(0),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Write Authority if present
if config.MintStrategy.Authority != nil {
// Write 1 to indicate Authority is present
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(1),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write KeyType
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(config.MintStrategy.Authority.KeyType),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write PublicKey length and bytes
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(len(config.MintStrategy.Authority.PublicKey)),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
if _, err := mintStrategyBytes.Write(
config.MintStrategy.Authority.PublicKey,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write CanBurn
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
config.MintStrategy.Authority.CanBurn,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
} else {
// Write 0 to indicate no Authority
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(0),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Write PaymentAddress if present
if len(config.MintStrategy.PaymentAddress) > 0 {
// Write length and bytes
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(len(config.MintStrategy.PaymentAddress)),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
if _, err := mintStrategyBytes.Write(
config.MintStrategy.PaymentAddress,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
} else {
// Write 0 to indicate no PaymentAddress
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(0),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Write FeeBasis if present
if config.MintStrategy.FeeBasis != nil {
// Write 1 to indicate FeeBasis is present
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(1),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write Type
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(config.MintStrategy.FeeBasis.Type),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Write Baseline as bytes
baselineBytes := config.MintStrategy.FeeBasis.Baseline.Bytes()
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint16(len(baselineBytes)),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
if _, err := mintStrategyBytes.Write(baselineBytes); err != nil {
return nil, errors.Wrap(err, "token config")
}
} else {
// Write 0 to indicate no FeeBasis
if err := binary.Write(
&mintStrategyBytes,
binary.BigEndian,
uint8(0),
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Pad to maximum size if necessary
mintStrategyData := mintStrategyBytes.Bytes()
if len(mintStrategyData) > 701 {
return nil, errors.Wrap(
errors.New("mint strategy data exceeds maximum size"),
"token config",
)
}
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"MintStrategy",
mintStrategyData,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Store Units (order 2)
if config.Units != nil {
unitsBytes := config.Units.FillBytes(make([]byte, 32))
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"Units",
unitsBytes,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Store Supply (order 3)
if config.Supply != nil {
supplyBytes := config.Supply.FillBytes(make([]byte, 32))
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"Supply",
supplyBytes,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
// Store Name (order 4)
nameBytes := []byte(config.Name)
// Truncate to 64 bytes if necessary
if len(nameBytes) > 64 {
nameBytes = nameBytes[:64]
}
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"Name",
nameBytes,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Store Symbol (order 5)
symbolBytes := []byte(config.Symbol)
// Truncate to 8 bytes if necessary
if len(symbolBytes) > 8 {
symbolBytes = symbolBytes[:8]
}
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"Symbol",
symbolBytes,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Store AdditionalReference (order 6)
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"AdditionalReference",
config.AdditionalReference[:],
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
// Store OwnerPublicKey (order 7)
if len(config.OwnerPublicKey) > 0 {
if err := rdfMultiprover.Set(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
TOKEN_BASE_DOMAIN[:],
"config:TokenConfiguration",
"OwnerPublicKey",
config.OwnerPublicKey,
tree,
); err != nil {
return nil, errors.Wrap(err, "token config")
}
}
return tree, nil
}
func unpackAndVerifyTokenConfigurationMetadata(
inclusionProver crypto.InclusionProver,
tree *qcrypto.VectorCommitmentTree,
) (*TokenIntrinsicConfiguration, error) {
commitment := tree.Commit(inclusionProver, false)
if len(commitment) == 0 {
return nil, errors.Wrap(errors.New("invalid tree"), "unpack and verify")
}
// Get the configuration metadata from index 16
tokenConfigurationMetadataBytes, err := tree.Get([]byte{16 << 2})
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
tokenConfigurationMetadata, err := qcrypto.DeserializeNonLazyTree(
tokenConfigurationMetadataBytes,
)
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
// Create an RDF multiprover for reading values using the schema
parser := &schema.TurtleRDFParser{}
rdfMultiprover := schema.NewRDFMultiprover(parser, inclusionProver)
config := &TokenIntrinsicConfiguration{}
// Read Behavior (order 0)
behaviorBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"Behavior",
tokenConfigurationMetadata,
)
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if len(behaviorBytes) < 2 {
return nil, errors.Wrap(
errors.New("invalid behavior bytes length"),
"unpack and verify",
)
}
config.Behavior = TokenIntrinsicBehavior(
binary.BigEndian.Uint16(behaviorBytes),
)
// Read MintStrategy (order 1)
mintStrategyBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"MintStrategy",
tokenConfigurationMetadata,
)
if err == nil && len(mintStrategyBytes) > 0 {
mintStrategy := &TokenMintStrategy{}
buf := bytes.NewReader(mintStrategyBytes)
// Read MintBehavior
var mintBehavior uint16
if err := binary.Read(buf, binary.BigEndian, &mintBehavior); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
mintStrategy.MintBehavior = TokenMintBehavior(mintBehavior)
// Read ProofBasis
var proofBasis uint16
if err := binary.Read(buf, binary.BigEndian, &proofBasis); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
mintStrategy.ProofBasis = ProofBasisType(proofBasis)
// Read VerkleRoot if present
var hasVerkleRoot uint8
if err := binary.Read(buf, binary.BigEndian, &hasVerkleRoot); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if hasVerkleRoot == 1 {
// Read VerkleRoot
var verkleLen uint16
if err := binary.Read(buf, binary.BigEndian, &verkleLen); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
verkleRoot := make([]byte, verkleLen)
if _, err := buf.Read(verkleRoot); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
mintStrategy.VerkleRoot = verkleRoot
}
// Read Authority if present
var hasAuthority uint8
if err := binary.Read(buf, binary.BigEndian, &hasAuthority); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if hasAuthority == 1 {
authority := &Authority{}
// Read KeyType
var keyType uint16
if err := binary.Read(buf, binary.BigEndian, &keyType); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
authority.KeyType = crypto.KeyType(keyType)
// Read PublicKey
var pubKeyLen uint16
if err := binary.Read(buf, binary.BigEndian, &pubKeyLen); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
pubKey := make([]byte, pubKeyLen)
if _, err := buf.Read(pubKey); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
authority.PublicKey = pubKey
// Read CanBurn
var canBurn bool
if err := binary.Read(buf, binary.BigEndian, &canBurn); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
authority.CanBurn = canBurn
mintStrategy.Authority = authority
}
// Read PaymentAddress if present
var paymentAddrLen uint16
if err := binary.Read(buf, binary.BigEndian, &paymentAddrLen); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if paymentAddrLen > 0 {
paymentAddr := make([]byte, paymentAddrLen)
if _, err := buf.Read(paymentAddr); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
mintStrategy.PaymentAddress = paymentAddr
}
// Read FeeBasis if present
var hasFeeBasis uint8
if err := binary.Read(buf, binary.BigEndian, &hasFeeBasis); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if hasFeeBasis == 1 {
feeBasis := &FeeBasis{}
// Read Type
var feeType uint16
if err := binary.Read(buf, binary.BigEndian, &feeType); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
feeBasis.Type = FeeBasisType(feeType)
// Read Baseline
var baselineLen uint16
if err := binary.Read(buf, binary.BigEndian, &baselineLen); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if baselineLen > 32 {
return nil, errors.Wrap(
errors.New("invalid baseline length"),
"unpack and verify",
)
}
if baselineLen != 0 {
baselineBytes := make([]byte, baselineLen)
if _, err := buf.Read(baselineBytes); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
feeBasis.Baseline = new(big.Int).SetBytes(baselineBytes)
} else {
feeBasis.Baseline = big.NewInt(0)
}
mintStrategy.FeeBasis = feeBasis
}
config.MintStrategy = mintStrategy
}
// Read Units (order 2)
unitsBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"Units",
tokenConfigurationMetadata,
)
if err == nil && len(unitsBytes) > 0 {
config.Units = new(big.Int).SetBytes(unitsBytes)
}
// Read Supply (order 3)
supplyBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"Supply",
tokenConfigurationMetadata,
)
if err == nil && len(supplyBytes) > 0 {
config.Supply = new(big.Int).SetBytes(supplyBytes)
}
// Read Name (order 4)
nameBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"Name",
tokenConfigurationMetadata,
)
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
config.Name = string(nameBytes)
// Read Symbol (order 5)
symbolBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"Symbol",
tokenConfigurationMetadata,
)
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
config.Symbol = string(symbolBytes)
// Read AdditionalReference (order 6)
additionalRefBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"AdditionalReference",
tokenConfigurationMetadata,
)
if err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
if len(additionalRefBytes) != 64 {
return nil, errors.Wrap(
errors.New("invalid additional reference length"),
"unpack and verify",
)
}
copy(config.AdditionalReference[:], additionalRefBytes)
// Read OwnerPublicKey (order 7)
ownerPublicKeyBytes, err := rdfMultiprover.Get(
TOKEN_CONFIGURATION_METADATA_SCHEMA,
"config:TokenConfiguration",
"OwnerPublicKey",
tokenConfigurationMetadata,
)
if err == nil && len(ownerPublicKeyBytes) > 0 {
config.OwnerPublicKey = ownerPublicKeyBytes
}
if err := validateTokenConfiguration(config); err != nil {
return nil, errors.Wrap(err, "unpack and verify")
}
return config, nil
}
func unpackAndVerifyConsensusMetadata(tree *qcrypto.VectorCommitmentTree) (
*qcrypto.VectorCommitmentTree,
error,
) {
return hg.UnpackConsensusMetadata(tree)
}
func unpackAndVerifyRdfHypergraphSchema(
tree *qcrypto.VectorCommitmentTree,
) (string, error) {
rdfSchema, err := hg.UnpackRdfHypergraphSchema(tree)
if err != nil {
return "", errors.Wrap(err, "unpack and verify")
}
return rdfSchema, nil
}
func unpackAndVerifySumcheckInfo(tree *qcrypto.VectorCommitmentTree) (
*qcrypto.VectorCommitmentTree,
error,
) {
return hg.UnpackSumcheckInfo(tree)
}