Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework capability API throughout specification #400

Merged
merged 10 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion misc/aspell_dict
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
personal_ws-1.1 en 579
personal_ws-1.1 en 586
ABCI
ABI
ADR
Agoric
Agoric's
Anca
Expand Down Expand Up @@ -102,6 +103,7 @@ RegisterIBCAccountPacketData
RegisterLightClient
RootOfTrust
RunTxPacketData
ScopedCapabilityKeeper
SignatureAndData
SourceIdentifier
SpeckleOS
Expand Down Expand Up @@ -140,6 +142,7 @@ addConnectionToClient
applyPrefix
atomicity
auth
authenticateCapability
authenticateTx
authenticationKey
authenticationPath
Expand Down Expand Up @@ -184,6 +187,7 @@ checkMisbehaviourAndUpdateState
checkSignature
checkValidityAndUpdateState
checkVersion
claimCapability
cleanupPacket
clientConnectionsKey
clientConnectionsPath
Expand Down Expand Up @@ -292,6 +296,7 @@ frozenKey
fungibility
generateAccount
generateAddress
getCapability
getChannelsUsingConnections
getCommitmentPrefix
getCompatibleVersions
Expand Down Expand Up @@ -376,6 +381,7 @@ namespaces
namespacing
newAddress
newCallbacks
newCapability
newCapabilityKey
newCapabilityPath
newPublicKey
Expand Down Expand Up @@ -475,6 +481,7 @@ refundTokens
relayer
relayerModule
relayers
releaseCapability
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
releasePort
remoteEnd
removeChannelFromConnection
Expand Down
Binary file modified spec.pdf
Binary file not shown.
36 changes: 17 additions & 19 deletions spec/ics-004-channel-and-packet-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In order to provide the desired ordering, exactly-once delivery, and module perm

`Connection` is as defined in [ICS 3](../ics-003-connection-semantics).

`Port` and `authenticate` are as defined in [ICS 5](../ics-005-port-allocation).
`Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation).

`hash` is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilising the channel. `hash` can be defined differently by different chains.

Expand Down Expand Up @@ -280,15 +280,14 @@ function chanOpenInit(

// optimistic channel handshakes are allowed
abortTransactionUnless(connection !== null)
abortTransactionUnless(authenticate(privateStore.get(portPath(portIdentifier))))
abortTransactionUnless(authenticateCapability(portPath(portIdentifier), capability))
channel = ChannelEnd{INIT, order, counterpartyPortIdentifier,
counterpartyChannelIdentifier, connectionHops, version}
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
key = generate()
provableStore.set(channelCapabilityPath(portIdentifier, channelIdentifier), key)
capability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
return key
return capability
}
```

Expand Down Expand Up @@ -318,7 +317,7 @@ function chanOpenTry(
previous.connectionHops === connectionHops &&
previous.version === version)
)
abortTransactionUnless(authenticate(privateStore.get(portPath(portIdentifier))))
abortTransactionUnless(authenticateCapability(portPath(portIdentifier), capability))
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
connection = provableStore.get(connectionPath(connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
Expand All @@ -334,11 +333,10 @@ function chanOpenTry(
channel = ChannelEnd{TRYOPEN, order, counterpartyPortIdentifier,
counterpartyChannelIdentifier, connectionHops, version}
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
key = generate()
provableStore.set(channelCapabilityPath(portIdentifier, channelIdentifier), key)
capability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
return key
return capability
}
```

Expand All @@ -354,7 +352,7 @@ function chanOpenAck(
proofHeight: uint64) {
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel.state === INIT || channel.state === TRYOPEN)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(portIdentifier, channelIdentifier))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
connection = provableStore.get(connectionPath(channel.connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
Expand Down Expand Up @@ -385,7 +383,7 @@ function chanOpenConfirm(
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === TRYOPEN)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(portIdentifier, channelIdentifier))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
connection = provableStore.get(connectionPath(channel.connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
Expand Down Expand Up @@ -415,7 +413,7 @@ Any in-flight packets can be timed-out as soon as a channel is closed.
function chanCloseInit(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(portIdentifier, channelIdentifier))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state !== CLOSED)
Expand All @@ -440,7 +438,7 @@ function chanCloseConfirm(
channelIdentifier: Identifier,
proofInit: CommitmentProof,
proofHeight: uint64) {
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(portIdentifier, channelIdentifier))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state !== CLOSED)
Expand Down Expand Up @@ -514,7 +512,7 @@ function sendPacket(packet: Packet) {
// optimistic sends are permitted once the handshake has started
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state !== CLOSED)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.sourcePort, packet.sourceChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier)
abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)
connection = provableStore.get(connectionPath(channel.connectionHops[0]))
Expand Down Expand Up @@ -567,7 +565,7 @@ function recvPacket(
channel = provableStore.get(channelPath(packet.destPort, packet.destChannel))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.destPort, packet.destChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability))
abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier)
abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier)

Expand Down Expand Up @@ -630,7 +628,7 @@ function acknowledgePacket(
channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.sourcePort, packet.sourceChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)

connection = provableStore.get(connectionPath(channel.connectionHops[0]))
Expand Down Expand Up @@ -693,7 +691,7 @@ function timeoutPacket(
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)

abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.sourcePort, packet.sourceChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)

connection = provableStore.get(connectionPath(channel.connectionHops[0]))
Expand Down Expand Up @@ -763,7 +761,7 @@ function timeoutOnClose(

channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
// note: the channel may have been closed
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.sourcePort, packet.sourceChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)

connection = provableStore.get(connectionPath(channel.connectionHops[0]))
Expand Down Expand Up @@ -832,7 +830,7 @@ function cleanupPacket(
channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)
abortTransactionUnless(authenticate(privateStore.get(channelCapabilityPath(packet.sourcePort, packet.sourceChannel))))
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)

connection = provableStore.get(connectionPath(channel.connectionHops[0]))
Expand Down
97 changes: 61 additions & 36 deletions spec/ics-005-port-allocation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,43 @@ and object references as used in Agoric's Javascript runtime ([reference](https:
type CapabilityKey object
```

`newCapability` must take a name and generate a unique capability key, such that the name is locally mapped to the capability key and can be used with `getCapability` later.

```typescript
function newCapability(name: string): CapabilityKey {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
```

`authenticateCapability` must take a name & a capability and check whether the name is locally mapped to the provided capability. The name can be untrusted user input.

```typescript
function authenticateCapability(name: string, capability: CapabilityKey): bool {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
```

`claimCapability` must take a name & a capability (provided by another module) and locally map the name to the capability, "claiming" it for future usage.

```typescript
function newCapabilityPath(): CapabilityKey {
// provided by host state machine, e.g. pointer address in Cosmos SDK
function claimCapability(name: string, capability: CapabilityKey) {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
```

`getCapability` must allow a module to lookup a capability which it has previously created or claimed by name.

```typescript
function getCapability(name: string): CapabilityKey {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
```

`releaseCapability` must allow a module to release a capability which it owns.

```typescript
function releaseCapability(capability: CapabilityKey) {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
```

Expand All @@ -95,33 +129,36 @@ function callingModuleIdentifier(): SourceIdentifier {
}
```

`generate` and `authenticate` functions are then defined as follows.

In the former case, `generate` returns a new object-capability key, which must be returned by the outer-layer function, and `authenticate` requires that the outer-layer function take an extra argument `capability`, which is an object-capability key with uniqueness enforced by the host state machine. Outer-layer functions are any functions exposed by the IBC handler ([ICS 25](../ics-025-handler-interface)) or routing module ([ICS 26](../ics-026-routing-module)) to modules.
`newCapability`, `authenticateCapability`, `claimCapability`, `getCapability`, and `releaseCapability` are then implemented as follows:

```
function generate(): CapabilityKey {
return newCapabilityPath()
function newCapability(name: string): CapabilityKey {
return callingModuleIdentifier()
}
```

```
function authenticate(key: CapabilityKey): boolean {
return capability === key
function authenticateCapability(name: string, capability: CapabilityKey) {
return callingModuleIdentifier() === name
}
```

In the latter case, `generate` returns the calling module's identifier and `authenticate` merely checks it.
```
function claimCapability(name: string, capability: CapabilityKey) {
// no-op
}
```

```typescript
function generate(): SourceIdentifier {
return callingModuleIdentifier()
```
function getCapability(name: string): CapabilityKey {
// not actually used
return nil
}
```

```typescript
function authenticate(id: SourceIdentifier): boolean {
return callingModuleIdentifier() === id
```
function releaseCapability(capability: CapabilityKey) {
// no-op
}
```

Expand All @@ -135,7 +172,6 @@ function portPath(id: Identifier): Path {
}
```


### Sub-protocols

#### Identifier validation
Expand All @@ -157,28 +193,17 @@ The IBC handler MUST implement `bindPort`. `bindPort` binds to an unallocated po
If the host state machine does not implement a special module manager to control port allocation, `bindPort` SHOULD be available to all modules. If it does, `bindPort` SHOULD only be callable by the module manager.

```typescript
function bindPort(id: Identifier) {
function bindPort(id: Identifier): CapabilityKey {
abortTransactionUnless(validatePortIdentifier(id))
abortTransactionUnless(privateStore.get(portPath(id)) === null)
key = generate()
privateStore.set(portPath(id), key)
return key
abortTransactionUnless(getCapability(portPath(id)) === null)
capability = newCapability(portPath(id))
return capability
}
```

#### Transferring ownership of a port

If the host state machine supports object-capabilities, no additional protocol is necessary, since the port reference is a bearer capability. If it does not, the IBC handler MAY implement the following `transferPort` function.

`transferPort` SHOULD be available to all modules.

```typescript
function transferPort(id: Identifier) {
abortTransactionUnless(authenticate(privateStore.get(portPath(id))))
key = generate()
privateStore.set(portPath(id), key)
}
```
If the host state machine supports object-capabilities, no additional protocol is necessary, since the port reference is a bearer capability.

#### Releasing a port

Expand All @@ -189,9 +214,9 @@ The IBC handler MUST implement the `releasePort` function, which allows a module
> Warning: releasing a port will allow other modules to bind to that port and possibly intercept incoming channel opening handshakes. Modules should release ports only when doing so is safe.

```typescript
function releasePort(id: Identifier) {
abortTransactionUnless(authenticate(privateStore.get(portPath(id))))
privateStore.delete(portPath(id))
function releasePort(capability: CapabilityKey) {
abortTransactionUnless(authenticateCapability(portPath(id), capability))
releaseCapability(capability)
}
```

Expand Down
5 changes: 3 additions & 2 deletions spec/ics-020-fungible-token-transfer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The `setup` function must be called exactly once when the module is created (per

```typescript
function setup() {
routingModule.bindPort("bank", ModuleCallbacks{
capability = routingModule.bindPort("bank", ModuleCallbacks{
onChanOpenInit,
onChanOpenTry,
onChanOpenAck,
Expand All @@ -84,6 +84,7 @@ function setup() {
onAcknowledgePacket,
onTimeoutPacketClose
})
claimCapability("port", capability)
}
```

Expand Down Expand Up @@ -221,7 +222,7 @@ function createOutgoingPacket(
bank.BurnCoins(sender, denomination, amount)
}
FungibleTokenPacketData data = FungibleTokenPacketData{denomination, amount, sender, receiver}
handler.sendPacket(Packet{destPort, destChannel, sourcePort, sourceChannel, data})
handler.sendPacket(Packet{destPort, destChannel, sourcePort, sourceChannel, data}, getCapability("port"))
}
```

Expand Down
Loading