Basic Kudos, Multichat, and Public Signal applications

The v2 specs must include basic forms of the Kudos, Multichat, and Public Signal applications.

Basic requirements for kudos:

  • Create kudo denomination
  • Send kudos
  • Swap kudos (Univ3-style configurable-price-curve positions)
  • Including multisignature accounts / account abstraction

Basic requirements for multichat:

  • Message objects, which have:
    • sender, an external identity (or account reference?)
    • receiver, an external identity (or account reference?)
    • timestamp, a timestamp
    • body, the body of the message
    • replies, a possibly empty set of message references
  • Moderation control descends hierarchically (in replies)

Basic requirements for Public Signal:

  • ProjectRequest objects, which have:
    • A description of what is requested
  • ProjectOffer objects, which have:
    • A description of what could be provided
    • A threshold of kudos needed to complete the project
  • Commitment objects, which have:
    • A link to owned kudos associated with this commitment
    • A reference to a Project

Then we need to think about logic for project description negotiation and recombination. Perhaps for the first version of the application this should be done manually.

Discuss! :computer_mouse:

2 Likes

Uniswap v3 if anyone is not familiar - https://uniswap.org/whitepaper-v3.pdf

Do we expect the message objects to be resources as opposed to CRDTs? @degregat @vveiln

Ah yes, by “object type” I mean roughly “resource kind”, with associated data formats.

1 Like

Kudos - I am not sure if up to date and there is newer pseudocode somewhere, but sharing @degregat work from prior thread for visibility

struct KudoResource {
    predicate: Predicate,   // kudo_logic
    label: FFElement,       // (Hash of the) ExternalIdentity of the Issuer, might need to be resolved to the actual ExtID
    quantity: FFElement,    // Encoding of Nat? Or are they also be native FFElements?
    value: KudoValue,       // Application Data
} // Predicate and Value will be encoded as Program Field Elements

// Kudos are fungible iff (kudoA1.predicate == kudoA2.predicate && kudoA1.label == kudoA2.label)

struct KudoValue {
    owner: ExternalIdentity,        
    owner_sig: Signature,
    issuer_sig: Signature, // Signature over Predicate and Label (Resource Type)
}

struct ExternalIdentity {
    key: Key,
}

impl ExternalIdentity {
    fn verify(self, bytes: [Byte], sig: Signature) -> bool {
        // return true is sig is valid signature from self
        // return false otherwise
    }
}

// Note: the execution environment (env) needs to provide the hash of each resource_logic to the respective predicates (hash_self), since they need it for verification, but we can't hash(self) within self, because there is no fixpoint

fn kudo_logic(ptx: Ptx) -> bool {
    for ri in ptx.resources.input {
        if ri.predicate == env.hash_self { // if the Resource is a Kudo
            if not ri.label.verify([ri.predicate.as_bytes()++ri.label.as_bytes()], r.value.issuer_sig) {
                return false // fail on invalid Issuer 1/2
            } // check if Value contains a valid signature of the Issuer, denoted by the label
              // since label is external Identity, we can call verification of a signature
        if not ri.value.owner.verify(ri.as_bytes, ri.value.owner_sig) {  
                return false  // if sig is valid, owner has authorized consumption
            }
        }
        // Check:
        // If a kudo without owner_sig is consumed, the only kudo that can be created is one that has the same contents + owner_sig
    }

    for ro in ptx.resources.output {
        if ro.predicate == env.hash_self {
            if ro.label.verify(r.value.issuer_sig) == false {
                return false // fail on invalid Issuer 2/2
            }
        }
        // Check:
        // Balance per owner and denom should be implicit to authorized consumption + balance check per denom 
    }
    return true
} // in addition to that, balance checks per denomination are always performed 

let valueA1 = KudoValue {
    owner: agentA,
    owner_sig: sig_agentA1,
    issuer_sig: type_sig_agentA1,
}

let kudoA1 = KudoResource {
    predicate: kudo_logic,
    label: hash(agentA)
    quantity: 1,
    value: valueA1,
}

let valueA2 = KudoValue {
    owner: agentA,
    owner_sig: sig_agentA2,
    issuer_sig: type_sig_agentA2,
}

let kudoA2 = KudoResource {
    predicate: kudo_logic,
    label: hash(agentA)
    quantity: 1,
    value: valueA2,
}

let valueB1 = KudoValue {
    owner: agentB,
    owner_sig: sig_agentB1,
    issuer_sig: type_sig_agentB1,
}

let kudoB1 = KudoResource {
    predicate: kudo_logic,
    label: hash(agentB)
    quantity: 1,
    value: valueB1,
}

let valueB2 = KudoValue {
    owner: agentB,
    owner_sig: sig_agentB2,
    issuer_sig: type_sig_agentB2,
}

let kudoB2 = KudoResource {
    predicate: kudo_logic,
    label: hash(agentB)
    quantity: 1,
    value: valueB2,
}

// Analogously for C1, C2


// To encode simple preferences for independent Kudos, agents can weigh each kudo. This encodes:
// how much they want to give up a certain Kudo, compared to the ones they offer,
// and how much they want a certain kudo, compared to the others they seek. 
//
// This enables, e.g. a CSP solver to optimize outcomes locally. 
type Weight = Float;

// Since Agents might know which type of Kudo they seek, but not all the details of the Resource, 
// we want to enable intents over Kudo Resource Types.
enum KudoIntent {
    KudoResource,
    KudoResourceType,   // We assume this to be a Kudo with the desired Predicate and Label + Value.owner set to the issuer of the intent.
                        // These should only be used for output resource types, to get the owner right. 
                        // Balance checks enforce inputs of the same type to exist.
}

// Each Agent can produce intents over which Kudos they offer and which Kudos they seek, for arbitrary amounts of input and output kudos.
struct Intent {
    input: Vec<(Weight, KudoIntent)>,
    output: Vec<(Weight, KudoIntent)>
} // this is encoded as a Ptx + solver hints to encode the weights

// For simplicities sake we only talk about Kudo Resources of quantity 1, but they could be arbitrary size.
// If bundles are split, the remainder is sent back to the owner before consumption.

// Example intents:
let intentA = Intent {
    input: vec![(1, kudoA1), (0.5, kudoA2)],
    ouput: vec![(1, TypeOfKudoC1), (1, TypeOfKudoB2)]
}

let intentB = Intent {
    input: vec![(0.5, kudoB1), (1, kudoB2)],
    ouput: vec![(1, TypeOfKudoC2), (0.2, TypeOfKudoA2)]
}

let intentC = Intent {
    input: vec![(1, kudoC1), (1, kudoC2)],
    ouput: vec![(1, TypeOfKudoB1), (1, TypeOfKudoA1)]
}

// Extra functionality the simulator needs:
// - signature scheme accessible via opcode
// - API to "send" a kudo to someone
// - API to check kudos I have

I have two questions:

  1. The requirements mentioned references (to accounts and messages (that are resources)).
    Maybe a general question first: How can resources that are consumed and created (e.g., in partial transactions) be referenced at all?
    If I understood it right from @vveiln’s Resource Machine Specification ART, the resource address is obtained as r.addr \equiv r.cm = h_{cm}(r) and the npk, nonce, and rseed of the resource changes with each consumption/creation.
    If the above is correct, does this imply that multichat message resources cannot be consumed/changed anymore once they have been sent, i.e., that they can’t be edited or deleted?

  2. What is meant by hierarchically descending moderation control?

1 Like

Just some thoughts about the first question:

Resources’ fields don’t change – resources are immutable, so once a resource is consumed and a new resource is created “from” the consumed resource, it is a completely different resource. So a resource has a static address. Resource’s status in the storage is not defined by if the resource is consumed or not (consumed resources do not necessarily get deleted from the storage), so you can store a consumed resource and access it by its address.

I think editing messages can be an application level feature: the old message gets consumed, the new (edited/empty) message is created, and the resource logic has a branch that performs some required checks for this case. In that case referencing a message would be a bit more complicated than just referencing the underlying resource, would probably require tracing the transactions with resources that were created “from” the consumed message resource, although it depends on what being consumed means for a message

3 Likes

Thinking about it, I guess this is really an interface feature, not a state machine one - in an interface, the original author of a Message could e.g. set some flag for deleted/hidden/etc. on replies to that message which a UI would then hide - but at the state machine level, we’re operating with immutable resources, there is no “deletion” - so never mind this for now, we don’t need it in the MVP.

1 Like

@apriori As @yulia explains, we could implement CRDTs on top of resources.

I think it would make sense to provide this abstraction in an application development library, since it seems useful for a bunch of different use cases.

2 Likes

The up to date pseudocode, intent solver prototypes by @AHart and juvix implementation by @jonathan can be found here: GitHub - anoma/kudos-snippets: Kudos as Intents

2 Likes