Skip to content

Instantly share code, notes, and snippets.

@zarutian
Last active May 9, 2024 12:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zarutian/2c3b0789b71c7599ec9515f0d50da04f to your computer and use it in GitHub Desktop.
Save zarutian/2c3b0789b71c7599ec9515f0d50da04f to your computer and use it in GitHub Desktop.
ActiveCapCerts - Work in progress, expect lot of edits/updates.

ActiveCapCerts

ActiveCapCerts, what are they?

In short they are cryptographically signed code texts of function bodies, that when instanciated into closures act onbehalf of their signer when those get invoked.

They were inspired by Nikita Borisov's "Active Certificates" paper but adapted to ocaps principles.

This means that the certs themselfs basically boil down each to datastructure of:

  • the "text" of the function
  • the public key (or cryptographic hash thereof) of the signer
  • cryptographic signiture of the above using the corrisponding private key of the signer.

And when such cert is instanciated into a closure, that closures environment consists of the safeEnv (which has handy but authority/powerless objects) augmented with a signer spefic facet to the gifts tables used by the NonceLocator of the CapTP subsystem.

That signer spefic facet is by the way nearly the only save way to pass authority/access/power around amongst ActiveCapCerts, and remote vats. (The other safe way to pass caps around is using the sealer/unsealer object pattern but it means the function of the cert must get the unsealer via aforesaid facet.)

But doesnt running unknown code, even in cap safe setup, open the receipiant vats up for availabity denial attacks?

Well, that entirely depends on the expressibility of the form of the "text" of the cert function can take. If you are just, say, evaluating ecmascript code then yes.

But what I have in mind basically boils down to a quasiLiteral snippet of a side of a CapTP connection conversation. I call it by the unimaginative name seqBlock, which is short of sequence block. Sequence from the sequence control flow form from Scheme and block from SmallTalk80/Squeak (also seen in Ruby).

The most recent implementation draft uses a rather simple form of seqBlocks, an array of actions that get performed in order. Each action is an array whose first (zeroth) element is a kind marker for what type of action it is. Some of the other elements are holes, some of the prefilled. These holes are in turn arrays whose first (zeroth) element is a kind marker for what type of hole it is. If you are thinking that this looks awfully lot like S-expresssions then you wont be far of, I just needed something quickly without having to write my own parser or AST walker. Plus, in the ecmascript implementation I could use Array.prototype methods to do much of the heavy/dreary lifting for me.

The kind of actions available are:

  • applyMethodSendOnly
  • applyMethod
  • applyFunctionSendOnly
  • applyFunction
  • get
  • assignOnce
  • tbd: assignInEnv

The first five are the equiv of Agoric Eventual send helper E() usage E.sendOnly(target).[verb](...args);, const resultPromise = E(target).[verb](...args);, (E.sendOnly(target)(...args);, const resultPromise = (E(target))(...args);, and const resultPromise = E.get(target, prop) respectively.

The last one/two is mostly for convience but also for selecting some other value for the seqBlock function to return as the return value are implictly the result promise of the last action in the sequence block.

The kinds of holes in each actions are:

  • "@qid" - the result promise of a spefic prior action in this sequence block. (Question ID)
  • "@sba" - one of or all the arguments given when the sequence block function was invoked. (Sequence Block Argument(s))
  • "@ref" - refers to thing external to the sequence block, not used in ActiveCapCerts usually. (REFerence)
  • "@dat" - an atomic datum like a number, string, bigint, or boolean. But is also used for 'quoting' combo datums like arrays. (DATa)
  • "@arr" - constructs an array, the elements are same kind of holes too. (ARRay)
  • "@rec" - constructs a record from an array of entries in style of ecmascripts Object.fromEntries(). (RECord)
  • "@env" - gets a thing out of, or the entire, environ of the closure of the sequence block function. (ENViron)
  • "@sqb" - constructs a sequence block function closure with given behaviour and environment. (SeQuenceBlock)

This means that the expressivity of sequence blocks is on par of a CapTP connection side. Yet none of the actions are blocking nor can a sequence block loop inside one event loop turn and block progress. This means that a vat that implements ActiveCapCerts no more vulernable than it was given the vat has a CapTP comms-system.

Thusly ActiveCapCerts can be nearly as ?powerful/expressive? as Nikita's original Active certificates.

Usage scenario 1.

Alice wants Bob to have access, unconditionally, to CarOb at VatC. Alice knows the cryptographic hash of one of Bobs public keys.

The ActiveCapCert would look like this:

text: [
  ["applyFunction", "q1", ["@env", "getLocalVatId"], ["@arr"]],
  ["applyMethod",   "q2", ["@env", "Ref"], "isSameEver" ["@arr", ["@dat", <VatId of VatC>], ["@qid", "q1"]]],
  ["assignOnce", "AlicePubkeyHash"
    ["@dat", <cryptographic hash of Alices public key signing this ACC>],
  ],
  ["applyMethod",   "q3", ["@env", "HandoffTable"], "acceptFrom",
    ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "CarObby"], ],
  ],
  ["applyMethodSendOnly", ["@env", "HandoffTable"], "provideFor",
    ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "CarObby"], ["@qid", "q3"] ],
  ],
  ["applyMethod", "q4", ["@qid", "q2"], "pick", ["@arr", ["@qid", "q3"], ["@dat", null]]],
  ["assignOnce", "BobPubkeyHash",
    ["@dat", <cryptographic hash of the public key of Bobs>],
  ],
  ["applyMethod", "vine", ["@env", "HandoffTable"], "provideFor",
    ["@arr", ["@qid", "BobPubkeyHash"], ["@sba", 0], ["@qid", "q4"]],
  ],
]
pubkey: <one of Alices public key>
signiture: <cryptographic signiture of above made with the corrisponding private key>

And the COSE.CBOR serialized version (annotated hexdump):

0000: 0xd9d9f7    -- Self described CBOR
0004: 0xd2        -- Tag 18  COSE Single Signer Data Object
0005: 0x84        -- . array of four items: protected hdr, unprotected hdr, payload, signature
0006: 0xd818      -- .. Tag 24  CBOR value in a bytestring
0008: 0x583a      -- ... bytestring of length 58 bytes
000a: 0xa4        -- .... map of four item pairs
000b: 0x01        -- ..... label 1 for algorithm
000c: 0x26        -- ..... -7 which is ECDSA w/ SHA-256
000d: 0x02        -- ..... label 2 for critical headers
000e: 0xa3010203  -- ..... [ alg, crit, content_type ]      
0012: 0x03        -- ..... label 3 for content type
0013: 0x781f4342
0017: 0x4f522f78
001b: 0x2d416374
001f: 0x69766543
0023: 0x61704365
0027: 0x72743056
002b: 0x34325061
002f: 0x796c6f61
0033: 0x64        -- ..... utf-8 string "CBOR/x-ActiveCapCert0V42Payload"
0034: 0x04        -- ..... label 4 for key identifier, which is not globally nor universially unique
0035: 0x4b416c69
0039: 0x63655075
003d: 0x626b6579
0041: 0x3433      -- ..... bstr whose contents are utf-8 string "AlicePubkey43"
0043: 0xa0        -- .. empty map
0044: 0xd818      -- .. tag 24  CBOR value in bytestring
0046: 0x59____    -- ... bytestring
0049: 0xa2        -- .... map of two item pairs
004a: 0x64746578
004e: 0x74        -- ..... utf-8 string "text"
004f: 0x88        -- ..... array of eight items
0050: 0x84        -- ...... array of four items
0051: 0x6d617070
0055: 0x6c794675
0059: 0x6e637469
005d: 0x6f6e      -- ....... utf-8 string "applyFunction"
005f: 0x627131    -- ....... utf-8 string "q1"
0062: 0x82        -- ....... array of two items
0063: 0x6440656e
0067: 0x76        -- ........ utf-8 string "@env"
0068: 0x6d6765744C6f63616c5661744964 -- ........ utf-8 string "getLocalVatId"
0076: 0x81        -- ....... array of one item
0077: 0x6440617272 -- ........ utf-8 string "@arr"
007C: 0x85        -- ...... array of five items
007D: 0x6b617070 
0081: 0x6C794d65
0085: 0x74686f64  -- ...... utf-8 string "applyMethod"
0089: 0x627132    -- ...... utf-8 string "q2"
008c: 0x82        -- ...... array of two items
008d: 0x6440656e
0091: 0x76        -- ....... utf-8 string "@env"
0092: 0x63526566  -- ....... utf-8 string "Ref"
0096: 0x6a697353
009a: 0x616d6545
009e: 0x766572    -- ...... utf-8 string "isSameEver"
00a1: 0x83        -- ...... array of three items
00a2: 0x64406172
00a6: 0x72        -- ....... utf-8 string "@arr"
00a7: 0x83        -- ....... array of two items
00a8: 0x64406461
00ac: 0x74        -- ........ utf-8 string "@dat"
00ad: 0xd9486c    -- ........ tag 18540  sha-256 hash
00b0: 0x5820____
00b4: 0x________
00b8: 0x________
00bc: 0x________
00c0: 0x________
00c4: 0x________
00c8: 0x________
00cc: 0x________
00d0: 0x____      -- ......... 20 byte long bytestring
00d2: 0x82        -- .......   array of two items
00d3: 0x64407169
00d7: 0x64        -- ........  utf-8 string "@qid"
00d8: 0x627131    -- ........  utf-8 string "q1"
00db: 0x83        -- .....     array of three items
00dc: 0x6a617373
00e0: 0x69676e4f
00e4: 0x6e6365    -- ......    utf-8 string "assignOnce"
00e7: 0x6f416c69
00eb: 0x63655075
00ef: 0x626b6579
00f3: 0x48617368  -- ......    utf-8 string "AlicePubkeyHash"
00f7: 0x82        -- ......    array of two items
00f8: 0x64406461
00fc: 0x74        -- .......   utf-8 string "@dat"
00fd: 0xd9486c    -- .......   tag 18540  sha-256 hash
0100: 0x5820____
0104: 0x________
0108: 0x________
010c: 0x________
0110: 0x________
0114: 0x________
0118: 0x________
011c: 0x________
0120: 0x____      -- ........ bytestring of length 20 bytes
0122: 0x85        -- .....    array of five items
0123: 0x6b617070 
0127: 0x6C794d65
012b: 0x74686f64  -- ...... utf-8 string "applyMethod"
012f: 0x637133    -- ...... utf-8 string "q3"
0132: 0x82        -- ...... array of two items
0133: 0x6440656e
0137: 0x76        -- ....... utf-8 string "@env"
0138: 0x6c48656e
013c: 0x646f6666
0140: 0x7461626c
0144: 0x65        -- ....... utf-8 string "HandoffTable"
0145: 0x6a616363
0149: 0x65707446
014d: 0x726f6d    -- ......  utf-8 string "acceptFrom"
0150: 0x83        -- ......  array of three items
0151: 0x64406172
0155: 0x72        -- .......  utf-8 string "@arr"
0156: 0x82        -- .......  array of two items
0157: 0x64407169
015b: 0x64        -- ........  utf-8 string "@qid"
015c: 0x6f416c69
0160: 0x63655075  
0164: 0x626b6579  
0168: 0x48617368  -- ........  utf-8 string "AlicePubkeyHash"
016c: 0x82        -- .......  array of two items
016d: 0x64406461
0171: 0x74        -- ........  utf-8 string "@dat"
0172: 0x67436172
0176: 0x4f626279  -- ........  utf-8 string "CarObby"
017a:

Which is equivaliant to this Agoric style EcmaScript in intent:

  let env = {}; // should be populated with:
  const ACC1_equiv = (...args) => {
    const q1 = E(env["getLocalVatId"])();
    const q2 = E(env["Ref"]).isSameEver("<VatId of VatC>", q1);
    const AlicePubkeyHash = "<cryptographic hash of Alices public key signing this ACC>";
    const q3 = E(env["HandoffTable"]).acceptFrom(AlicePubkeyHash, "CarObby");
    E.sendOnly(env["HandoffTable"]).provideFor(AlicePubkeyHash, "CarObby", q3);
    const q4 = E(q2).pick(q3, null);
    const BobPubkeyHash = "<cryptographic hash of the public key of Bobs>";
    const vine = E(env["HandoffTable"]).provideFor(BobPubkeyHash, args[0], q4);
    return vine;
  };

Usage scenario 2.

Alice wants Bob to have access to her ColourLazerHyperJet printer via her VatA, but only to print in grayscale, only to print 100 pcs A4 pages, and do so before an expiration date.

The ActiveCapCert would look like:

text: [
  ["assignOnce", "AlicePubkeyHash",
    ["@dat", <Alice Publickey Hash>],
  ],
  ["assignOnce", "BobPubkeyHash",
    ["@dat", <Bob Publickey Hash>],
  ],
  ["applyMethod", "clock", ["@env", "HandoffTable"], "acceptFrom",
    ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "UTC_Clock"]],
  ],
  ["applyMethodSendOnly",  ["@env", "HandoffTable"], "provideFor",
    ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "UTC_Clock"], ["@qid", "clock"]],
  ],
  ["applyMethod",   "printer", ["@env", "HandoffTable"], "acceptFrom",
    ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "ColourLazerHyperJet_one"]],
  ],
  ["applyMethodSendOnly", ["@env", "HandoffTable"], "provideFor", ["@arr", ["@qid", "AlicePubkeyHash"], ["@dat", "ColourLazerHyperJet_one"], ["@qid", "printer"]]],
  ["applyFunction", "q1", ["@env", "getLocalVatId"], ["@arr"]],
  ["applyMethod",   "q2", ["@env", "Ref"], "isSameEver" ["@arr", ["@dat", <VatId of VatA>], ["@qid", "q1"]],
  ["applyMethod",   "q3", ["@qid", "clock"], "isCurrentTimeBefore", ["@arr", ["@dat", "2023-12-06T04:20Z"]]],
  ["applyMethod",   "q4", ["@qid", "q2"], "pick", ["@arr", ["@qid", "q3"], ["@dat", false]]],
  ["assignOnce", "printerFacet_gPQL",
    ["@sqb", ["@dat",
      [
        ["applyMethod", "result", ["@env", "realPrinter"], "getPrintQueueLength", ["@sba"]],
      ],
    ], ["@rec", ["@arr",
      ["@arr", ["@dat", "realPrinter"], ["@qid", "printer"]],
    ]]],
  ],
  ["assignOnce", "printerFacet_pIC",
    ["@sqb", ["@dat",
      [
        ["applyFunction", "t1", ["@env", "makePromiseKit"], ["@arr"]],
        ["get", "t2", ["@qid", "t1"], ["@dat", "reject"]],
        ["applyFunctionSendOnly", ["@qid", "t2"], ["@arr", ["@dat", "printing in colour is denied via this printer facet."]]],
        ["get", "result", ["@qid", "t1"], ["@dat", "promise"]],
      ],
    ], ["@rec", ["@arr",
      ["@arr", ["@dat", "makePromiseKit"], ["@env", "makePromiseKit"]],
    ]]],
  ],
  ["assignOnce", "allowance_depleted_printer"
    ["@rec", ["@arr",
      ["@arr", ["@dat", "printOnePageInGreyScale"],
        ["@sqb", ["@dat",
          [
            ["applyFunction", "t1", ["@env", "makePromiseKit"], ["@arr"]],
            ["get", "t2", ["@qid", "t1"], ["@dat", "reject"]],
            ["applyFunctionSendOnly", ["@qid", "t2"], ["@arr", ["@dat", "the page allowance for this printer facet is depleted"]]],
    ["get", "result", ["@qid", "t1"], ["@dat", "promise"]],
          ],
        ], ["@rec", ["@arr",
          ["@arr", ["@dat", "makePromiseKit"], ["@env", "makePromiseKit"]],
        ]]],
      ],
    ]],
  ],
  ["assignOnce", "printerFacet_pIGS",
    ["@sqb", ["@dat",
      [
        ["applyMethod",   "t1", ["@env", "HandoffTable"], "acceptFrom", ["@arr", ["@env", "AlicePubkeyHash"], ["@dat", "Bobs_page_allowance"]]],
        ["applyMethod",   "t2", ["@qid", "t1"], "subtract", ["@arr", ["@dat", 1n]]],
        ["applyMethodSendOnly", ["@env", "HandoffTable"], "provideFor", ["@arr", ["@env", "AlicePubkeyHash"], ["@dat", "Bobs_page_allowance"], ["@qid", "t2"]]],
        ["applyMethod",   "t3", ["@qid", "t2"], "isGreaterThan", ["@arr", ["@dat", 0n]]],
        ["applyMethod",   "t4", ["@qid", "t3"], "pick", ["@arr", ["@env", "realPrinter"], ["@env", "allowance_depleted_printer"]],
        ["applyMethod",   "result", ["@qid", "t4"], "printOnePageInGreyScale", ["@sba"]],
      ],
    ], ["@rec", ["@arr",
      ["@arr", ["@dat", "AlicePubkeyHash"], ["@qid", "AlicePubkeyHash"]],
      ["@arr", ["@dat", "HandoffTable"],    ["@env", "HandoffTable"]],
      ["@arr", ["@dat", "realPrinter"],     ["@qid", "printer"]],
      ["@arr", ["@dat", "allowance_depleted_printer"], ["@qid", "allowance_depleted_printer"]],
    ]]],
  ],
  ["assignOnce", "printerFacet",
    ["@rec", ["@arr",
      ["@arr", ["@dat", "getPrintQueueLength"], ["@qid", "printFacet_gPQL"]],
      ["@arr", ["@dat", "printOnePageInColour"], ["@qid", "printFacet_pIC"]],
      ["@arr", ["@dat", "printOnePageInGreyScale"], ["@qid", "printFacet_pIGS"]],
    ]]
  ],
  ["applyMethod", "útkoma", ["@qid", "q4"], "pick", ["@arr", ["@qid", "printerFacet"], ["@dat", null]]],
  ["applyMethod", "vine",   ["@env", "HandoffTable"], "provideFor", ["@arr", ["@qid", "BobPubkeyHash"], ["@dat", "grayScalePrinter"], ["@qid", "útkoma"]]],
]
pubkey: <one of Alices public key>
signiture: <cryptographic signiture of above made with the corrisponding private key>

Usage scenario 3.

Bob wants to grant Simone revocable access to CarObby at VatC that Alice granted him access to in Usage scenario 1.

The ActiveCapCert would look like:

text: [
  ["assignOnce", "SimonePubkeyHash", ["@dat", <hash of Simones pubkey>]],
  ["assignOnce", "AlicePubkeyHash",  ["@dat", <hash of Alices pubkey whose privkey she used to sign the cert in usage scenario 1>]],
  ["assignOnce", "BobPubkeyHash",    ["@dat", <hash of Bobs public key that is mentioned in that aforesaid ACC>]],
  ["applyMethod", 1n, ["@env", "HandoffTable"], "isGiftWaiting", ["@arr", ["@qid", "BobPubkeyHash"], ["@dat", "Simone membraned CarOb"]]],
  ["assignOnce", "tr", ["@sqb", ["@dat", [
    ["applyMethod", "pair", ["@env", "HandoffTable"], "acceptFrom", ["@arr", ["@env", "BobPubkeyHash"], ["@dat", "Simone membraned CarOb"]]],
    ["applyMethodOnly", ["@env", "HandoffTable"], "provideFor", ["@arr", ["@env", "BobPubkeyHash"], ["@dat", "Simone membraned CarOb"], ["@qid", "pair"]]],
    ["assignOnce", "skilagildi", ["@qid", "pair"]],
  ]], ["@rec", ["@arr",
    ["@arr", ["@dat", "HandoffTable"], ["@env", "HandoffTable"]],
    ["@arr", ["@dat", "BobPubkeyHash"], ["@qid", "BobPubkeyHash"]],
  ]]]],
  ["assignOnce", "fa", ["@sqb",
    ["@dat", [
      ["applyMethod", 1n, ["@env", "ActiveCapCert"], "fromCBOR", ["@arr", ["@dat", <the entire CBOR encoded COSE cert from usage scenario 1>]]],
      ["applyFunction", "vine", ["@qid", 1n], ["@arr", ["@dat", "AliceCarObby"]]],
      ["applyMethod", 2n, ["@env", "HandoffTable"], "acceptFrom", ["@arr", ["@env", "AlicePubkeyHash"], ["@dat", "AliceCarObby"]]],
      ["applyFunction", 3n, ["@env", "makeRevocableMembrane"], ["@arr", ["@qid", 2n]]],
      ["applyMethodOnly", ["@env", "HandoffTable"], "provideFor", ["@arr", ["@env", "BobPubkeyHash"], ["@dat", "Simone membraned CarOb"], ["@qid", 3n]]],
      ["assignOnce", "útkoma", ["@qid", 3n]],
    ]],
    ["@rec", ["@arr",
      ["@arr", ["@dat", "ActiveCapCert"], ["@env", "ActiveCapCert"]],
      ["@arr", ["@dat", "AlicePubkeyHash"], ["@qid", "AlicePubkeyHash"]],
      ["@arr", ["@dat", "BobPubkeyHash"], ["@qid", "BobPubkeyHash"]],
      ["@arr", ["@dat", "HandoffTable"], ["@env", "HandoffTable"]],
    ]],
  ]],
  ["applyMethod", 2n, ["@qid", 1n], "pick", ["@arr", ["@qid", "tr"], ["@qid", "fa"]]],
  ["applyFunction", 3n, ["@qid", 2n], ["@arr"]],
  ["get", "membranedCarObby", ["@qid", 3n], ["@dat", 0]],
  ["get", "revoker", ["@qid", 3n], ["@dat", 1]],
  ["applyMethod", "vine", ["@env", "HandoffTable"], "provideFor", ["@arr", ["@qid", "SimonePubkeyHash"], ["@dat", "CarOb"], ["@qid", "membranedCarObby"]]],
]
pubkey: <Bobs public key Alice named in her granting acc>
signiture: <apropos cryptographic sigentured where the corrisponding private key was used>

Usage scenario 4.

old chat message:

so, I mentioned I had been working on something I call ActiveCapCerts. What are those you ask? Basically each CBOR encoded seqBlock inside of CWT single signed envelope. Okay, but what is a seqBlock? The name is shortening of sequence block. Sequence from Scheme and blocks from SmallTalk80/Squeak (and possibly Ruby). But what is it? Well think of it as an quasiLiteral array of eventual sends wrapped in a closure. Each entry having 'holes' that each, depending on kind of hole, will be filled with a promise for result of an earlier send in the seqBlock, an argument given when invoking the closure, or a value from the environment of the closure. In the case of ActiveCapCerts, each get an environment that is the safeEnv augmented with three things, an ACC deserializer that instanciates a closure from ACC and cert issuer spefic facets to the gifting mechanism from the 3rd party handoff in CapTP of E.

various links

tbd

  1. should the kind of holes and kind of actions be enumarated? That is small positive ints be interchangable with each ?symbol-utf8string?. main pro: smaller cbor encoding which can then pack higher kolomorov complexity into the ACCs.
  2. list the entire safe env w/the async ops plus the powers? (powers: handofftable facet and ActiveCapCert closure from bstr constructor)
  3. add in the fromSwiss power, that works thusly: four item array of bytestrings is given:
    • the vatid where the refered object lives
    • the swissHash of the object
    • the hash of the recipiant public key (same as first param to acceptFrom/provideFor)
    • the hmac of the preceeding three using the swissNum as the hmac key then in an ACC one does HandoffTable.acceptFrom("", swissHash) to retrive the resulting gift deposited. This ACC must be executed at the vat that hosts the gift.

certlet based una made up of ActiveCapCerts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment