5 个版本 (3 个破坏性更新)

0.4.0 2020年6月8日
0.3.0 2020年6月5日
0.2.1 2020年3月21日
0.2.0 2020年2月24日
0.1.0 2020年2月22日

#7 in #constructs

Apache-2.0

86KB
1K SLoC

密码构造语言

摘要

CCLang 是一种新语言,受到比特币脚本启发,将所有密码构造序列化为一系列数据令牌和命令令牌,描述构造的使用和应用。例如,一个密钥会被序列化为一个加密密钥,并带有解密密钥的命令。任何想访问密钥的人都可以执行脚本,如果他们提供正确的输入,结果将是解密后的密钥。这适用于从简单的密钥存储到最复杂的如多因素密钥轮换方案的所有密码构造。

动机

通过将密码构造以“函数”形式存储,我们抽象出底层的密码私有的/操作提供者,并提供了用于描述任意复杂密码构造的标准化语言(例如,DID 文档,区块链交易记录,加密数据包等)。

这来自于 DID 文档标准化工作,当我们遇到需要存储用于不同控制器绑定和密钥轮换操作复杂密码构造的需求时。这些操作通常有一种或多种方式,控制器可以证明对身份的控制,或强制从受损密钥轮换。本 RFC 寻求标准化我们用于描述这些构造的语言,无论底层加密库如何,都可以重新创建构造并执行操作。

这还来自于 did:git DID 方法规范工作,当我们遇到需要描述用于强制执行仓库治理规则密码构造的需求时。一个例子规则是:对 MAINTAINERS 文件的更改需要由至少2位项目维护者签名的提交。规则需要以机器可解析和可执行的形式序列化,并具有针对文件和提交的上下文特定引用,以读取用于构造创建的数据。本 RFC 再次寻求标准化我们用于描述这些规则的语言。

为了进一步阐述如何在仓库中更改MAINTAINERS文件时强制执行现有维护者的M-of-N签名,以下示例展示了如何实现。MAINTAINERS文件可以包含多个用于该文件本身治理的CCLang脚本。或者规则可以存储在另一个GOVERNANCE文件中。这无关紧要。

假设CCLang M-of-N检查脚本存储在MAINTAINERS文件中。它假设在执行之前,维护者公钥列表将首先压入栈中,然后它只是将维护者公钥的数量加起来,并检查这个总和是否大于或等于M阈值。

现在,如果提交上的CCLang并行多签名被编写成在每次有效数字签名上都留下验证公钥的副本,那么为了强制执行M-of-N,提交钩子脚本只需要将MAINTAINERS文件中的M-of-N CCLang脚本追加到提交中的CCLang多签名末尾,然后执行即可。

前半部分将是提交中的CCLang多签名,并将所有有效签名的公钥留在栈上。然后后半部分将是M-of-N检查脚本,它将每个公钥与MAINTAINERS文件中的公钥列表进行比较,并计算匹配的数量,并与M阈值进行比较。如果在最后栈上留下TRUE,则提交通过了维护者的M-of-N多签名检查。如果栈上留下FALSE,则未通过检查,提交应被拒绝。

如果一个代码仓库要求所有提交都必须由存储在仓库本身的身份签名,并且所有提交钩子都使用CCLang强制执行加密检查,并且所有加密检查CCLang脚本也都存储在仓库中的文件中,那么整个过程是自我认证的,并保证是正确的。

指南级说明

加密结构语言(CCLang)用于序列化所有加密结构,并使用映射到Ursa提供的加密算法和操作的运算。Ursa抽象了不同算法的多个实现(例如SHA256、RSA加密等)。在编译时,开发者可以选择他们想要包含在最终Ursa二进制文件中的实现。CCLang是Ursa API的扩展,通过将Ursa API调用编码为一系列数据和令牌,指导Ursa API的使用。

CCLang受比特币脚本启发,是一种逆波兰表示法(RPL)脚本语言,设计用于在虚拟栈机器上执行。CCLang脚本包含数据令牌、参数令牌和操作令牌,以序列化执行特定加密操作所需的步骤(例如验证数字签名、解密密钥、验证密钥旋转操作等)。任何开发者通常会编码用于加密操作的数 据的地方,他们都会存储一个CCLang脚本。

重要的是要指出,CCLang的范围仅限于加密操作,并不打算支持其他操作,如联系预言机、网络查找或任何其他现有加密库不会处理的操作。它旨在通过序列化形式(例如网络数据包、DID文档等)标准化我们如何共享复杂的加密结构。

一些示例

我认为真正理解CCLang如何不同的最佳方法是通过比较现有序列化方案和同一结构在CCLang中的序列化方式。请不用担心理解这些示例中的所有令牌,因为它们将在下面的参考部分中详细描述。

密钥材料

最基本的加密结构是存储公钥。在提出的DID文档规范中,公钥以JSON格式序列化,编码类型在键中

{ "type": "Ed25519", "hexkey": "0a7d1d784358af1f8073ba07eb5ae2fc7272a860ec4547de8bc13d04259cd59a" }

Secure Scuttlebutt 做的事情与众不同。他们使用一个符号字符来识别公钥——@——然后在后面添加一个后缀来定义密钥预期要使用的算法

@Cn0deENYrx+Ac7oH61ri/HJyqGDsRUfei8E9BCWc1Zo=.ed25519

在 CCLang 中,单独的公钥不需要任何装饰。它只是作为密钥数据存储。但是,如果密钥必须编码为特定的格式,序列化方式会有所变化。当以文本格式存储时,公钥通常使用十六进制字符或 Base64 或 Base58 等二进制到文本转换方案进行编码。因此,在文本格式中,CCLang 形式的 Base64 编码公钥看起来如下所示

Cn0deENYrx+Ac7oH61ri/HJyqGDsRUfei8E9BCWc1Zo= Base64 DECODE

第一个令牌是 Base64 编码的公钥数据,后面跟着编码方案的标识符(例如,“Base64”),最后是一个执行解码操作的操作码。由于 CCLang 是使用堆栈机器处理的,这是将密钥解码为字节的操作的反波兰表示法(RPN)。

要执行此操作,从左到右处理令牌。首先将编码的密钥数据推入堆栈。然后推入指定文本到二进制方案的标识符。DECODE 操作码从堆栈中弹出编码方案标识符和编码的字节,执行正确的解码函数,然后将结果字节推入堆栈。任何理解 CCLang 的软件都会执行此脚本以获取内存中的公钥字节,以便在其他操作中使用。

数字签名

数字签名是我们开始接触到稍微复杂一些的加密结构的时候。创建数字签名最常见的方法是首先对要签名的数据进行哈希处理,然后使用一个随机数(nonce)和公钥加密来加密数据的哈希。生成的数字签名通常是原始数据、加密的哈希、签名人公钥和生成签名时使用的随机数的组合。要验证数字签名,验证者使用随机数和公钥来解密哈希,然后将其与他们对签名的数据进行计算得到的哈希进行比较。

在可验证凭证中,他们存储一个名为“链接数据签名”的东西,它包含凭证的数字签名。它们编码在 JSON-LD 中,如下所示

{
  "@context": "https://www.w3.org/2018/credentials/examples/v1",
  "title": "Hello World!",
  "proof": {
    "type": "Ed25519Signature2018",
    "proofPurpose": "assertionMethod",
    "created": "2019-08-23T20:21:34Z",
    "verificationMethod": "did:example:123456#key1",
    "domain": "example.org",
    "jws": "eyJ0eXAiOiJK...gFWFOEjXk"
  }
}

注意,类型是一个复杂的标识符,它暗示了一个哈希函数和一个加密函数,并依赖于外部文档来指定 Ed25519Signature2018 的确切构建方式。实现者必须阅读此外部文档,才能了解如何处理每个字段以验证此签名。链接数据签名只有轻微的自我描述性。验证签名的步骤留给实现者,并假设是广为人知的。链接数据签名规范未能具体说明验证签名的步骤,这给新接触密码学的人带来了问题。

在 Secure Scuttlebutt 中,出于某种原因,他们放弃了用于所有其他内容的符号字符,而是通过在后缀中添加 ".sig" 字符串来表示某物是签名。所使用的哈希算法在 Secure Scuttlebutt 协议指南中指定,并且不编码在签名中。这限制了 Secure Scuttlebutt 在不造成破坏性变化和不兼容实现的情况下采用新的签名方案的能力。二进制到文本编码方案也在协议规范中指定为非 URL 安全的 Base64。同样,这也限制了未来对协议进行修订的机会。唯一具有自我描述性的部分是后缀中的加密算法标识符。Secure Scuttlebutt 签名如下所示

QYOR/zU9dxE1aKBaxc3C0DJ4gRyZtlMfPLt+CGJcY73sv5abKKKxr1SqhOvnm8TY784VHE8kZHCD8RdzFl1tBA==.sig.ed25519

使用CCLang,数字签名是全面自描述的,不依赖于外部规范来存储验证数字签名的过程。CCLang的数字签名是脚本,在执行时验证数字签名。CCLang数字签名的主要挑战在于引用在计算签名时被哈希的外部数据。CCLang使用“打开-读取-关闭”序列以及特定于应用程序的标识符来指定要打开什么数据存储单元(例如文件、流、对象),以及从数据存储单元中读取哪些字节用于哈希计算。

如果我们假设以下是一个名为 foo.txt 的文本文件的数字签名,并且整个文件都被签名了,所使用的二进制到文本编码方案是十六进制,所使用的哈希算法是SHA256,所使用的公钥加密算法是Ed25519,那么CCLang数字签名看起来如下所示

418391ff353d77113568a05ac5cdc2d03278811c99b6531f3cbb7e08625c63bdecbf969b28a2b1af54aa84ebe79bc4d8efce151c4f24647083f11773165d6d04
Hex DECODE 1425ffb6c0cba6e6c23ca29f22bc3881cf924241dc683d7bb3b188ea2ff38966 Hex
DECODE foo.txt OPEN 0 $ READ CLOSE Ed25519 VERIFY

为了理解和验证这个数字签名,需要按照以下方式执行

  1. 将十六进制数字签名推送到栈中,然后是 Hex 编码标识符和将文本解码为二进制的操作码。结果是栈顶的数字签名二进制数据。
  2. 接下来,将签名者的公钥从十六进制解码并也留在栈上。
  3. 然后,将文件名---foo.txt---推送到栈中,然后执行 OPEN 操作码弹出名称,打开文件,并将流句柄推送到栈上。
  4. 接下来,将读取的起始索引---0---和要读取的字节数---$---推送到栈中。$ 符号表示“流结束”,这将导致 READ 操作码读取文件中的所有字节并将它们推送到栈上,然后是流句柄。
  5. 然后关闭文件流,这将关闭文件并将流句柄从栈中弹出,留下文件字节在栈顶。
  6. 最后一步是将签名算法标识符 'Ed25519' 推送到栈中,然后执行 'VERIFY' 操作码以验证签名数据、公钥和签名数据。'VERIFY' 操作码的结果是是否将 'TRUE' 或 'FALSE' 推送到栈中,以指示签名是否有效。

通过执行CCLang数字签名脚本,我们通过将签名、公钥和签名数据放入栈中,然后运行签名验证函数来验证数字签名。重要的是要注意,OPEN 操作码的参数---在本例中为 foo.txt---完全是应用程序特定的。上面的CCLang脚本是对文件系统中的文件进行数字签名,因此使用相对路径来引用文件。还假定签名存储在同一目录中的单独文件中,称为“附加签名”。如果此签名作为 foo.txt 文件的一部分存储,则 READ 操作码将不会使用 $,而是指定读取的字节数,但不包括数字签名本身。

如果这是对区块链数据存储中事务的数字签名,则 OPEN 操作的参数将是事务标识符——无论是事务的哈希地址还是区块链使用的任何寻址方案。

另外还有一个需要注意的要点。在签名的数据被编码在JSON中,且签名本身也是JSON的一部分的系统中,CCLang脚本可以通过多次读取来读取数字签名前后字节,用于哈希。这比Secure Scuttlebutt和可验证凭证规范期望的数字签名验证方式要优越得多,并且不需要解析和操作整个JSON文档。

例如,在Secure Scuttlebutt中,数字签名存储如下: "signature": "<base64>.sig.ed25519"。规范要求解析JSON,移除签名字段,然后在哈希之前使JSON标准化。使用CCLang,如果签名数据从偏移量152开始,长度为100字节,那么CCLang版本的签名将有两个READ操作,然后是一个CONCAT操作,如下所示

0 152 READ 251 $ READ CONCAT

这将使栈上的JSON字节准备好被解析、标准化,然后哈希。如果假设JSON已经标准化,则可以直接计算哈希。

多签签名

CCLang在多签签名的编码方面优于可验证凭证和Secure Scuttlebutt。多签签名是由两个或更多身份的签名组合而成的数字签名。有两种类型的多签签名:并行和串行。

并行签名是指多个身份对相同数据进行签名。这是一个简单的协议。想象一下多个当事人签署同一份合同。他们都同意签署的数据。串行签名是指一个身份先对某些数据进行签名,然后第二个身份对包含第一个签名的数据进行签名。下一个签名将签署数据和所有之前的签名。这是一个认可协议,其中每个后续签名都认可数据和之前的签名。这在监管角色(如开源项目中的维护者)中很有用。开发者提交已签名的提交,然后维护者在将其合并到代码的主分支时签名提交和开发者的签名,从而认可贡献。

目前,可验证凭证规范没有直接指定如何序列化这两种类型的多签。只要每个单独的签名遵循规范,它就是一个符合规范的签名,由实施者决定。

在Secure Scuttlebutt协议规范中,根本没有涉及多签签名。虽然有一些关于支持这一点的讨论,但还没有达成共识,主要是因为所有明显的方法都是丑陋的解决方案,而且到目前为止需求很低。

使用CCLang,这两种类型的多签签名都很容易实现。通过使用IF-ELSE-FI操作码三联组,很容易将数字签名附加起来形成一个多签签名。例如,让我们从一个基本的数字签名开始,如下所示

<sig hex> Hex DECODE <pub key hex> Hex DECODE foo.txt OPEN 0 $ READ CLOSE Ed25519 VERIFY

假设另一个签署者想要在上面的签名中添加一个并行签名。他们会对自己的签名进行计算,并使用IF-ELSE-FI将其附加到CCLang脚本中,以验证数据上的两个签名

<first sig hex> Hex DECODE <first pub key hex> Hex DECODE OPEN 0 $ READ CLOSE ED25519 VERifY IF <second sig hex> Hex DECODE <second pub key hex> Hex DECODE foo.txt OPEN 0 $ READ CLOSE Ed25519 VERIFY ELSE FALSE FI

这是一个并行多签名。第一部分是一个普通的CCLang数字签名验证。在VERIFY操作码之后,栈顶部将要么是一个TRUE(如果签名有效),要么是一个FALSE(如果签名无效)。IF操作码弹出栈顶元素,如果它是TRUE,则执行IFELSE之间的脚本,否则执行ELSEFI之间的脚本。在IFELSE之间有一个第二数字签名验证脚本,如果签名有效,则将TRUE留在栈顶。因此,如果两个签名都有效,脚本将以栈顶的TRUE结束。如果第一个签名无效,则执行ELSEFI之间的代码。该脚本仅将FALSE推入栈中,以指示签名检查失败。可以独立地将任意数量的并行签名附加到数字签名上。这允许进行异步多签名操作,其中一个人签署某些数据,然后将数据和他们的签名转发给下一个人,然后那个人附加他们的签名,依此类推,直到所有签名都创建并附加。

对于串行多签名,后续的签名必须包含数据以及之前的签名。为了实现这一点,在执行验证操作码之前,必须设置背书签名的CCLang脚本来读取数据和读取之前的签名数据。

可以使用IF-ELSE-FI模式生成并附加任意数量的数字签名到现有的签名。也可以混合并行和串行数字签名,任意排列。假设有一个由两个协作的作者签署的提交。该提交有两个并行签名,每个作者一个。然后,项目维护者将其与他们的串行签名合并,该签名涵盖了提交和作者的两个签名。

参考级别解释

CCLang系统的核心是用于执行CCLang脚本的栈机。一般来说,CCLang栈机是一个抽象的栈机,可以接受任何类型的数据。长字符串被存储,并推入栈中。操作码可能从栈中弹出参数,也可能将结果推入栈中。如果由于输入参数类型不正确或输入参数值无效等原因,无法执行操作码,则CCLang脚本的执行将立即停止,并返回适当的错误代码给CCLang解释器的调用者。

记录操作码

本参考的其余部分使用Forth编程语言中命令记录的标准表示法。Forth也是一种RPN栈编程语言。它使用类似以下形式的简单命令记录:

/ a1 -- argument one is data of any type.
/ a2 -- second argument of any type.
= ( a1 a2 -- TRUE|FALSE )

注释行以单个 / 字符开始,位于命令和堆栈文档之前,用于解释堆栈图中的参数。堆栈图跟随命令(在本例中为 =)——由一个右括号 ( 后跟用 -- 分隔的从堆栈中弹出的参数,然后是推入堆栈的返回参数组成。在 = 操作码的情况下,它弹出任意类型的两个参数——a1 和 a2——并比较它们是否相等,如果相等则推入 TRUE,如果不相等则推入 FALSE

另一个例子是 DECODE 操作码

/ e -- encoded data.
/ t -- encoding type identifier.
/ b -- decoded binary data.
DECODE ( e t -- b )

DECODE 操作码从堆栈中弹出编码数据和编码类型标识符,执行正确的解码操作将文本转换为二进制,然后将二进制推入堆栈。这仅用于文本序列化格式,如 JSON、YAML 或 XML。

操作码

逻辑比较

/ a1 -- data of any type.
/ a2 -- data of any type.
= ( a1 a2 -- TRUE|FALSE )

= 操作符弹出任意类型的两个参数并比较它们是否相等。如果相等则推入 TRUE,如果不相等则推入 FALSE。

/ a1 -- numerical data.
/ a2 -- numerical data.
< ( a1 a2 -- TRUE|FALSE )

< 操作符从堆栈中弹出两个数值参数,并比较第一个弹出的参数是否小于第二个弹出的参数。如果是,则推入 TRUE,否则推入 FALSE。

/ a1 -- numerical data.
/ a2 -- numerical data.
> ( a1 a2 -- TRUE|FALSE )

> 操作符从堆栈中弹出两个数值参数,并比较第一个弹出的参数是否大于第二个弹出的参数。如果是,则推入 TRUE,否则推入 FALSE。

还有 <=>=!= 操作码,它们是上述逻辑比较的组合。它们分别测试小于等于、大于等于和不等。

二进制到文本操作

/ e -- text-encoded data.
/ t -- encoding type identifier.
/ b -- decoded binary data.
DECODE ( e t -- b )

DECODE 操作码用于解码使用某些二进制到文本编码系统(如 Base64、Base58 和/或十六进制)编码的二进制数据。支持的编码类型列表如下。

/ b -- binary data.
/ t -- encoding type identifier.
/ e -- binary data encoded as text.
ENCODE ( b t -- e )

ENCODE 操作码用于使用指定的二进制到文本编码方案将二进制数据编码为文本格式。结果是编码的二进制文本数据。

加密

/ e -- binary encrypted data.
/ k -- binary key data.
/ o.. -- optional binary parameters required by the given algorithm (e.g. nonce).
/ i -- encryption algorithm identifier.
/ b -- decrypted binary data.
DECRYPT ( e k o.. i -- b )

DECRYPT 操作码用于使用密钥和任何其他所需数据以及算法标识符指定的算法解密加密数据。结果是解密后的二进制数据。

/ b -- binary data.
/ k -- binary key data.
/ o.. -- optional binary parameters.
/ i -- encryption algorithm identifier.
/ e -- encrypted binary data.

ENCRYPT 操作码用于使用指定的加密算法和密钥以及算法特定的参数加密二进制数据。结果是加密数据。

签名

/ s -- binary signature data.
/ k -- binary public key.
/ d -- binary data that was signed.
/ o.. -- optional binary signature parameters.
/ i -- signature algoirthm identifier.
VERIFY ( s k o.. i -- TRUE|FALSE )

'VERIFY' 操作码执行与签名算法指定的数字签名验证函数关联的操作。它弹出签名、公钥、已签名的数据以及任何可选参数和标识符,如果签名有效则推入 'TRUE',如果无效则推入 'FALSE'。

/ d -- binary data to be signed.
/ k -- binary secret key to sign with.
/ o.. -- binary optional parameters for the signature scheem.
/ i -- signature algorithm identifier.
SIGN ( d k o.. i -- s )

'SIGN' 操作码使用密钥对提供的数据创建一个分离的数字签名。所有参数都会弹出,并将生成的签名推入堆栈。

哈希

/ b -- binary data.
/ i -- hashing algorithm identifier.
/ h -- hash of the data.
HASH ( b i -- h )

HASH 操作符使用指定的哈希算法对二进制数据进行哈希。结果是数据的哈希值。

数据输入/输出

/ i -- data storage object identifier (e.g. file name, transaction number, etc)
/ m -- mode to open the file under.
/ h -- handle to the opened data storage object.
OPEN ( i m -- h )

OPEN 操作码是应用特定的,因为数据存储对象标识符是特定于应用的。在某些情况下,它可能是要打开的文件名,或者是区块链应用中的事务标识符,或者是应用中有意义的任何数据对象引用。模式是一个代表读取、写入和追加的标识符。结果是打开对象的句柄,可以被其他数据I/O命令使用。

/ h -- handle to the opened data storage object.
/ s -- zero-indexed starting offset to begin the read.
/ n -- number of bytes to read from the object.
/ b -- binary read from the data object.
READ ( h s n -- b h )

READ 操作码获取打开的数据存储对象的句柄,并从给定的偏移量开始读取指定字节数。结果是读取的数据对象的二进制数据,然后是顶部打开数据对象的句柄。

/ h -- handle to the opened data storage object.
/ b -- binary data to write.
WRITE ( h b -- h )

WRITE 操作码获取要写入的句柄和数据,并将其写入打开的数据对象。结果是数据对象的句柄。

/ h -- handle to the opened data storage object.
/ n -- number of bytes and direction to seek in.
SEEK ( h n -- h )

SEEK 操作码查找指定的字节数。如果字节数是负数,则向0索引字节反向查找。如果数字是正数,则向对象的最后一个字节正向查找。结果是打开数据对象的句柄。

/ h -- handle to the opened data storage object.
CLOSE ( h -- )

CLOSE 操作码关闭打开的数据存储对象。它从堆栈中弹出句柄,并将任何东西推入堆栈。

数据操作

/ b -- binary data.
CONCAT ( b b -- b )

CONCAT 操作码从堆栈中弹出两个二进制数据参数,并将顶部参数连接到堆栈中下面的参数的末尾,并将结果二进制数据推回堆栈。

/ b -- binary data.
/ o -- integer offset.
/ c -- integer count.
SLICE ( b o c -- b )

SLICE 操作码从堆栈中弹出计数、偏移量和二进制数据,并创建一个新的从偏移量开始,从原始二进制数据参数中获取计数字节数的新二进制数据参数。结果推入堆栈。偏移量是零索引的,偏移量和计数都是以字节为单位。

data: abcdef0123456789
         ^ 3 offset ^ 12 count

result: def012345678
/ b -- binary data.
| ( b b -- b )

| 操作码是顶部两个二进制数据参数之间的位或。结果推入堆栈。

/ b -- binary data.
& ( b b -- b )

& 操作码是顶部两个二进制数据参数之间的位与。结果推入堆栈。

/ b -- binary data.
^ ( b b -- b )

^ 操作码是顶部两个二进制数据参数之间的位异或。结果推入堆栈。

/ b -- binary data.
~ ( b -- b )

~ 操作码是顶部二进制数据参数的位反。结果推入堆栈。

堆栈控制

/ a -- argument of any type.
DUP ( a -- a a )

DUP 操作码弹出顶部项,复制它,并将原始项及其副本推入堆栈的顶部。

/ a -- argument of any type.
POP ( a -- )

POP 操作码从堆栈中弹出顶部项,并忘记它。这用于丢弃堆栈的顶部。

流程控制

/ b -- boolean argument.
IF ( b -- ) ELSE FI

IF-ELSE-FI 操作码三元组和相关的 IF-FI 操作码对用于在CCLang脚本中进行条件分支。 IF 操作码弹出堆栈的顶部,并将其评估为布尔参数。

IF-ELSE-FI的情况下,如果参数为真,则执行IFELSE之间的脚本,然后跳转到FI之后的脚本。如果为假,则执行ELSEFI之间的脚本。

IF-FI的情况下,如果参数为真,则执行IFFI之间的脚本。如果为假,则流程跳转到FI操作码之后的脚本。

编码格式

CCLang的第一个版本支持以下编码类型

  • 十六进制 -- 以小写十六进制字符表示的二进制。
  • Base64 -- 第62个和第63个字符分别是+/。没有行长度限制;所有数据在一行中,必须使用=进行填充。
  • Base64Url -- 第62个和第63个字符分别是-_。没有行长度限制;所有数据在一行中,没有填充。
  • Base58 -- 请参阅这里的定义。

加密算法

CCLang的第一个版本支持以下加密算法

  • XSalsa20Poly1305

签名算法

CCLang的第一个版本支持以下签名算法

  • Ed255519

哈希算法

CCLang的第一个版本支持以下哈希算法

  • SHA256
  • SHA512

序列化格式

CCLang是一个抽象语言定义,不规定数据或操作码在特定格式中的序列化方式。每种编码格式都留给它自己指定如何执行。以下是JSON的示例编码规范。

JSON-CCLang

将抽象CCLang映射到JSON的第一项工作是决定用于支持的编码类型、加密算法、哈希算法和操作码的字符串常量。以下是CCLang中的常量列表及其在JSON中的字符串常量等效项。

编码常量
  • 十六进制 - Hex
  • Base64 - Base64
  • Base64Url - Base64Url
  • Base58 - Base58
加密算法
  • XSalsa20Poly1305 - XSalsa20Poly1305
签名算法
  • Ed25519 - Ed25519
哈希算法
  • SHA256 - SHA256
  • SHA512 - SHA512
操作码
  • Equal - =
  • NotEqual - !=
  • LessThan - <
  • LessThanEqual - <=
  • GreterThan - >
  • GreaterThanEqual - >=
  • DECODE - DECODE
  • ENCODE - ENCODE
  • DECRYPT - DECRYPT
  • ENCRYPT - ENCRYPT
  • SIGN - SIGN
  • VERIFY - VERIFY
  • HASH - HASH
  • OPEN - OPEN
  • READ - READ
  • 写入 - WRITE
  • 查找 - SEEK
  • 关闭 - CLOSE
  • 连接 - CONCAT
  • 切片 - SLICE
  • 复制 - DUP
  • 弹出 - POP
  • 如果 - IF
  • 否则 - ELSE
  • 结束 - FI
编码

CCLang 脚本以 JSON 列表中的项目形式存储。因此,一个简单的十六进制编码的公开字符串将被编码为

{
  "key": [ "0a7d1d784358af1f8073ba07eb5ae2fc7272a860ec4547de8bc13d04259cd59a", "Hex", "DECODE" ]
}

缺点

唯一的缺点是,结果加密结构不够紧凑。它们可以是非常长的字符串,对于不熟悉逆波兰表示法(RPN)的人来说,可能难以理解正在发生什么。其他加密结构表示法的紧凑性是通过对规格中不易更新且不随数据本身传输的结构细节进行固定实现的。这使得所有其他格式都非自描述。这使得新系统很难确切知道必须做什么才能利用任何特定的加密结构。

理由和替代方案

CCLang 的理由是标准化一个用于序列化加密结构的自描述格式。随着我们开发出更多使用越来越复杂结构的新应用,采用自描述的序列化形式变得越来越重要。不再是我们只需要存储公钥,然后编写规范来说明“始终使用算法 X 解密数据”的时代了。随着各种类型的多签名和零知识证明(ZKPs)的日益普及,我们的系统可以通过使用自描述格式得到极大的增强。这也使得实现者更容易将 CCLang 映射到他们所使用的底层加密库。

现有技术

像 Git 和 Secure Scuttlebutt 这样的系统,以及由 DID 驱动的系统,都在努力适应它们所运行的新多签名和 ZKP 世界。到目前为止,还没有提出任何好的方案来适应 Git 的多签名,Secure Scuttlebutt 现在已经完全拒绝了多签名。有人尝试将 DIDs 中使用的 Linked Data Signatures 适应多签名。所提出的方案与 CCLang 有点相似,但很笨拙,并且没有利用堆栈机器和语言的优雅解决方案。

如引言所述,CCLang 受比特币脚本的启发,并从 Forth 编程语言中汲取了灵感。比特币脚本原本是一个很好的替代方案,但它具有专为比特币交易设计的、只有在比特币交易上下文中才有意义的操作码。CCLang 力求成为一个更通用的设计方案,以实现与比特币脚本相同的功能。事实上,CCLang 可以用来实现比特币脚本中的所有功能,但以一种稍微冗长的方式。比特币脚本的操作码,如 OP_CHECKLOCKTIMEVERIFY,可以使用一系列 CCLang 操作码来实现,这些操作码从比特币交易中读取 nLockTime,并使用 IF-ELSE-FI 来执行 OP_CHECKLOCKTIMEVERIFY 所做的相同事情。

在 Christopher Allen 关于更智能签名的帖子中有一些其他现有技术记录 [0]。在那篇博客文章中,他讨论了受 Forth 脚本(如比特币脚本)启发的函数式编程解决方案。他还引用了 Peter Todd 的 Dex 脚本,该脚本使用类似 Lisp 的 s-表达式语法 [1]

最终,像比特币脚本这样的自描述系统的优势使得它比 Git 和 Secure Scuttlebutt 以及 DID 文档使用的“规范辅助”系统更好。比特币的脚本太特定于应用程序,不能成为一个好的通用解决方案,因此创建 CCLang 来填补这个空白。

未解决的问题

  • 我们应该确保CCLang是非图灵完备的吗?这些本质上是小的“智能合约”,因此它们是安全关键的。非图灵完备性将允许创建静态分析工具和确定性的评估,以寻找不希望出现的情况。
  • CCLang是否应该包含一个宏定义系统,其中可以使用宏名来将常见的CCLang操作码序列进行别名替换?这将需要一个宏设置脚本,该脚本将在执行任何CCLang脚本之前隐式运行,以确保在执行使用这些宏的脚本之前处理并初始化所有宏定义。这会有用的一个好例子是解码Base58Check比特币地址。Base58Check地址的编码和解码需要连接,字节掩码,以及多个SHA256散列用于校验部分。拥有一个名为Base58CheckDecode的宏可以使CCLang脚本更具可读性。
  • 一些加密库如NaCl在其更复杂的结构(如密封盒)中隐藏了许多“子操作”。这是故意的,目的是使库不易误用,并隐藏许多内部细节。要在不定义新操作码的情况下将CCLang映射到NaCl中,这些新操作码直接映射到NaCl中的密钥封装/解封装调用,这将是一个挑战。

参考资料

依赖项

~22MB
~153K SLoC