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

CAIP-10 #2

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
113 changes: 113 additions & 0 deletions caip10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
namespace-identifier: icp-caip10
title: ICP Namespace - Chains
author: Quint Daenen (@q-uint)
discussions-to: TODO
status: Draft
type: Standard
created: 2023-05-24
requires: [ CAIP-2, CAIP-10 ]
---

# CAIP-10

*For context, see the [CAIP-10][] specification.*
q-uint marked this conversation as resolved.
Show resolved Hide resolved

## Rationale

In the world of blockchain technology, the need to identify and differentiate various entities within a decentralized
network is crucial. CAIP-10 addresses this challenge by providing a standardized method to identify an account in any
blockchain specified by the CAIP-2 blockchain ID.
Comment on lines +19 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe accounts are tightly bound to the ledger to which they belong.
I'd say the ledger principal should be a part of the address.

Something like

icp:...:icrc1:k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-6cc627i.1@ryjl3-tyaaa-aaaaa-aaaba-cai

or, if we use path-like addressing, something like

/icp/.../icrc1/ryjl3-tyaaa-aaaaa-aaaba-cai/k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-6cc627i.1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CAIP-10 limits (for reasons unknown to me) the length of a full account identifier to 128 characters. I'm not sure what the consequences are if ICP were to violate that part of the spec.


On the Internet Computer the concept of identification takes the form of principals. Principals serve as generic
identifiers for users, smart contracts known as canisters, and potentially other entities in the future. Principals play
a similar role to addresses on Ethereum Virtual Machine (EVM) chains, where they can refer to contracts and users ids.

In the context of the Internet Computer, principals are considered opaque binary blobs, typically ranging in length from
0 to 29 bytes. Importantly, there intentionally exists no mechanism to distinguish between canister IDs and user IDs.
This design choice allows for flexibility and future extensibility, enabling the Internet Computer to accommodate new
concepts and entities as they emerge.

## Syntax

There are different forms of principals, but this is out of scope for this specification. The specification will only
focus on the textual representation of a principal.

The textual representation of a `principal p` is `Text(Base32(CRC32(p) · p))` where

- `CRC32` is a four byte check sequence, calculated as defined by ISO 3309 and stored as big-endian.

- `Base32` is the Base32 encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-6), with no
padding character added.

- The middle dot (`.`) denotes concatenation.

- `Text` takes an ASCII string and inserts the separator `-` (dash) every 5 characters. The last group may contain
less than 5 characters. A separator never appears at the beginning or end.

The textual representation is conventionally printed with lower case letters, but parsed case-insensitively.

Because the maximum size of a principal is 29 bytes, the textual representation will be no longer than 63 characters (10
times 5 plus 3 characters with 10 separators in between them).

```shell
function textual_encode() {
( echo "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin; echo -n "$1" ) |
xxd -r -p | base32 | tr A-Z a-z |
tr -d = | fold -w5 | paste -sd'-' -
}

function textual_decode() {
echo -n "$1" | tr -d - | tr a-z A-Z |
fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = |
base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr a-z A-Z
}
```

The `account_id` is a case-sensitive string in the form

```
account_id: chain_id + ":" + account_address
chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [CAIP-2][])
account_address: [-.%a-zA-Z0-9]{1,128}
```

## Test Cases

```
# Zero Principal
icp:737ba355e855bd4b61279056603e0550:aaaaa-aa

# Anonymous Principal
icp:737ba355e855bd4b61279056603e0550:2vxsx-fae

# User Principal
icp:737ba355e855bd4b61279056603e0550:g27xm-fnyhk-uu73a-njpqd-hec7y-syhwe-bd45b-qm6yc-xikg5-cylqt-iae

# Canister Principal (ICP Ledger)
icp:737ba355e855bd4b61279056603e0550:ryjl3-tyaaa-aaaaa-aaaba-cai
```

## Considerations

### Account Ids

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Ledger & Tokenization Working Group agreed on a textual representation for accounts; here is the spec: https://github.com/dfinity/ICRC-1/pull/98/files#diff-cf72e3d78c946edc91e3b8d499b8c7a7e894daaca1922e33b9ab7e40544e5030
I hope we won't need to invent a new encoding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into this by next WG session.


Within the ledger, an account is identified by its address, which is derived from the principal ID and the sub-account
identifier. For better understanding, you can compare principal identifiers to the hash of a user's public key in
systems like Bitcoin or Ethereum. To authenticate and perform operations on the principal's account in the ledger
canister, the corresponding secret key is used to sign messages. Additionally, canisters themselves can possess accounts
within the ledger canister, and in such cases, the address is derived from the principal of the canister.

It is possible for an account owner to have authority over more than one account. In such cases, each account
corresponds to a pair consisting of an account owner and a sub-account. The sub-account is an optional bitstring that

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add an example that includes the principal an a subaccount? Or do we hit length constraints again, making this impossible?
If so, we could maybe keep the current scheme for the main account (because it's nice to directly have the principal in there) and the have a hash that includes the subaccount identifier for subaccounts?

serves the purpose of distinguishing between different sub-accounts belonging to the same owner.

## References

- [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md)
- [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md)
- [Principal IDs](https://internetcomputer.org/docs/current/references/ic-interface-spec#principal)

## Rights

Copyright and related rights waived via CC0.
12 changes: 12 additions & 0 deletions go/caip10/principal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package caip10

import "github.com/aviate-labs/agent-go/principal"

func TextualEncode(raw []byte) string {
return principal.Principal{Raw: raw}.String()
}

func TextualDecode(str string) ([]byte, error) {
p, err := principal.Decode(str)
return p.Raw, err
}
23 changes: 23 additions & 0 deletions go/caip10/principal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package caip10_test

import (
"fmt"
"github.com/aviate-labs/agent-go/principal"
"github.com/icvc/icp-namespace/go/caip10"
)

func Example_textual() {
p := caip10.TextualEncode([]byte{0x00})
rp, _ := caip10.TextualDecode(p)
fmt.Println(p)
fmt.Printf("%02x\n", rp)
// Output:
// 2ibo7-dia
// 00
}

func Example_binary() {
fmt.Println(principal.AnonymousID.String())
// Output:
// 2ibo7-dia
}
10 changes: 10 additions & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/icvc/icp-namespace/go

go 1.20

require github.com/aviate-labs/agent-go v0.2.0

require (
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
)
6 changes: 6 additions & 0 deletions go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/aviate-labs/agent-go v0.2.0 h1:zEIWDtIDuLsMfaOCmjWG9unfXkadap89qBTXLpzOzGg=
github.com/aviate-labs/agent-go v0.2.0/go.mod h1:+k1JEhxfIHhh5GNLmH7YM3ptqbMN8L45qdcgbMq/qM4=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
16 changes: 16 additions & 0 deletions shell/caip10.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

function textual_encode() {
( echo "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin; echo -n "$1" ) |
xxd -r -p | base32 | tr A-Z a-z |
tr -d = | fold -w5 | paste -sd '-' -
}

function textual_decode() {
echo -n "$1" | tr -d - | tr a-z A-Z |
fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = |
base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr a-z A-Z
}

# textual_encode \00
# textual_decode "2ibo7-dia"