@canvas-js/signed-cid
Advanced tools
Comparing version 0.8.26-alpha.4 to 0.8.26-main-488c798d
@@ -5,6 +5,8 @@ import { create as createDigest } from "multiformats/hashes/digest"; | ||
import { getDigest } from "./digests.js"; | ||
import { encode as eip712Encode } from "./eip712.js"; | ||
export function getCID(value, options = {}) { | ||
const [codec, digest] = [getCodec(options), getDigest(options)]; | ||
const hash = digest.digest(codec.encode(value)); | ||
const isRaw = codec.code === 0x55 && digest.code === 0x55; | ||
const hash = digest.digest(codec.encode(isRaw ? eip712Encode(value) : value)); | ||
return CID.createV1(codec.code, createDigest(digest.code, hash)); | ||
} |
@@ -38,2 +38,17 @@ import { sha256 } from "@noble/hashes/sha256"; | ||
}, | ||
{ | ||
// this corresponds to the "raw" digest in the multicodec spec | ||
// https://github.com/multiformats/multicodec/blob/696e701b6cb61f54b67a33b002201450d021f312/table.csv#L41 | ||
name: "raw", | ||
code: 0x55, | ||
digest: (iter) => { | ||
const parts = []; | ||
for (const chunk of iter) { | ||
for (const char of chunk) { | ||
parts.push(char); | ||
} | ||
} | ||
return new Uint8Array(parts); | ||
}, | ||
}, | ||
]; | ||
@@ -40,0 +55,0 @@ export const defaultDigest = "sha2-256"; |
@@ -6,8 +6,36 @@ type Codec = { | ||
}; | ||
export declare function dynamicAbiEncodeArgs(args: Record<string, any>): string; | ||
export declare function getAbiEncodeParametersArguments(args: Record<string, any>): { | ||
/** | ||
* This codec generates CIDs for (a subset of) JSON objects using | ||
* ABI encoding, and TypedDataEncoder for hashing. | ||
* | ||
* Objects may contain nested objects, address strings, strings, numbers, | ||
* or booleans, which are mapped to `bytes`, `address`, `string`, `int256`, | ||
* or `bool` respectively. | ||
* | ||
* We enforce that numbers are integers, and 40-byte-long strings beginning | ||
* with "0x" are always encoded as addresses. | ||
* | ||
* | ||
* While the codec is implemented for dynamically typed data, if you are | ||
* writing an onchain verifier for offchain signed data, it must still be | ||
* statically typed to a specific action schema beforehand. See | ||
* @canvas-js/ethereum-contracts for examples. | ||
*/ | ||
export declare const eip712Codec: Codec; | ||
/** | ||
* Encode an argument object `Record<string, any>` as an | ||
* ABI-encoded bytestring. | ||
*/ | ||
export declare function getAbiString(args: Record<string, any>): string; | ||
/** | ||
* Convert an argument object `Record<string, any>` to a | ||
* set of EIP712-compatible types. | ||
* | ||
* This is exposed separately from `abiEncodeArgs` so both are | ||
* available for tests. | ||
*/ | ||
export declare function getEIP712Args(args: Record<string, any>): { | ||
types: string[]; | ||
values: any[]; | ||
}; | ||
export declare const eip712Codec: Codec; | ||
export {}; |
@@ -1,43 +0,21 @@ | ||
import * as web3 from "web3"; | ||
import { getBytes } from "ethers"; | ||
import { AbiCoder } from "ethers/abi"; | ||
import { TypedDataEncoder } from "ethers/hash"; | ||
export function dynamicAbiEncodeArgs(args) { | ||
const { types, values } = getAbiEncodeParametersArguments(args); | ||
return web3.eth.abi.encodeParameters(types, values); | ||
} | ||
function getAbiTypeForValue(value) { | ||
if (typeof value === "string") { | ||
if (value.match(/^0x[0-9a-fA-F]{40}$/)) { | ||
return "address"; | ||
} | ||
else { | ||
return "string"; | ||
} | ||
} | ||
else if (typeof value === "number") { | ||
// if is integer | ||
if (Number.isInteger(value)) { | ||
return "int256"; | ||
} | ||
else { | ||
throw new TypeError(`non-integer numbers are not yet supported`); | ||
} | ||
} | ||
else if (typeof value === "boolean") { | ||
return "bool"; | ||
} | ||
throw new TypeError(`invalid type ${typeof value}`); | ||
} | ||
export function getAbiEncodeParametersArguments(args) { | ||
const sortedArgs = Object.keys(args).sort(); | ||
const types = []; | ||
const values = []; | ||
for (const key of sortedArgs) { | ||
types.push("string"); | ||
values.push(key); | ||
types.push(getAbiTypeForValue(args[key])); | ||
values.push(args[key]); | ||
} | ||
return { types, values }; | ||
} | ||
/** | ||
* This codec generates CIDs for (a subset of) JSON objects using | ||
* ABI encoding, and TypedDataEncoder for hashing. | ||
* | ||
* Objects may contain nested objects, address strings, strings, numbers, | ||
* or booleans, which are mapped to `bytes`, `address`, `string`, `int256`, | ||
* or `bool` respectively. | ||
* | ||
* We enforce that numbers are integers, and 40-byte-long strings beginning | ||
* with "0x" are always encoded as addresses. | ||
* | ||
* | ||
* While the codec is implemented for dynamically typed data, if you are | ||
* writing an onchain verifier for offchain signed data, it must still be | ||
* statically typed to a specific action schema beforehand. See | ||
* @canvas-js/ethereum-contracts for examples. | ||
*/ | ||
export const eip712Codec = { | ||
@@ -64,3 +42,5 @@ name: "eip712", | ||
}; | ||
hashedPayload = TypedDataEncoder.hash({}, types, { | ||
hashedPayload = TypedDataEncoder.hash({ | ||
name: message.topic, | ||
}, types, { | ||
clock: message.clock, | ||
@@ -94,3 +74,5 @@ parents: message.parents, | ||
}; | ||
hashedPayload = TypedDataEncoder.hash({}, types, { | ||
hashedPayload = TypedDataEncoder.hash({ | ||
name: message.topic, | ||
}, types, { | ||
clock: message.clock, | ||
@@ -100,3 +82,3 @@ parents: message.parents, | ||
name: message.payload.name, | ||
args: dynamicAbiEncodeArgs(message.payload.args), | ||
args: getAbiString(message.payload.args), | ||
address: message.payload.address.split(":")[2], | ||
@@ -115,1 +97,55 @@ blockhash: message.payload.blockhash || "", | ||
}; | ||
/** | ||
* Encode an argument object `Record<string, any>` as an | ||
* ABI-encoded bytestring. | ||
*/ | ||
export function getAbiString(args) { | ||
const { types, values } = getEIP712Args(args); | ||
return new AbiCoder().encode(types, values); | ||
} | ||
/** | ||
* Convert an argument object `Record<string, any>` to a | ||
* set of EIP712-compatible types. | ||
* | ||
* This is exposed separately from `abiEncodeArgs` so both are | ||
* available for tests. | ||
*/ | ||
export function getEIP712Args(args) { | ||
const sortedArgs = Object.keys(args).sort(); | ||
const types = []; | ||
const values = []; | ||
for (const key of sortedArgs) { | ||
types.push("string"); | ||
values.push(key); | ||
types.push(getAbiTypeForValue(args[key])); | ||
values.push(args[key]); | ||
} | ||
return { types, values }; | ||
} | ||
/** | ||
* Get the type of a dynamically typed JS argument for typehash | ||
* generation. | ||
*/ | ||
function getAbiTypeForValue(value) { | ||
if (typeof value === "string") { | ||
if (value.match(/^0x[0-9a-fA-F]{40}$/)) { | ||
return "address"; | ||
} | ||
else { | ||
return "string"; | ||
} | ||
} | ||
else if (typeof value === "number") { | ||
// if is integer | ||
if (Number.isInteger(value)) { | ||
return "int256"; | ||
} | ||
else { | ||
throw new TypeError(`non-integer numbers are not yet supported`); | ||
} | ||
} | ||
else if (typeof value === "boolean") { | ||
return "bool"; | ||
} | ||
throw new TypeError(`invalid type ${typeof value}`); | ||
} |
export type { Signature, Signer } from "@canvas-js/interfaces"; | ||
export { getAbiString, getEIP712Args, encode as eip712Encode } from "./eip712.js"; | ||
export * from "./Ed25519Signer.js"; | ||
@@ -3,0 +4,0 @@ export * from "./Secp256k1Signer.js"; |
@@ -0,1 +1,2 @@ | ||
export { getAbiString, getEIP712Args, encode as eip712Encode } from "./eip712.js"; | ||
export * from "./Ed25519Signer.js"; | ||
@@ -2,0 +3,0 @@ export * from "./Secp256k1Signer.js"; |
@@ -24,3 +24,5 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
const encodingLength = varint.encodingLength(Secp256k1Signer.code); | ||
const publicKey = secp256k1.getPublicKey(privateKey); | ||
// must return the uncompressed public key so that we can compute the corresponding ethereum | ||
// address | ||
const publicKey = secp256k1.getPublicKey(privateKey, false); | ||
const bytes = new Uint8Array(encodingLength + publicKey.byteLength); | ||
@@ -27,0 +29,0 @@ varint.encodeTo(Secp256k1Signer.code, bytes, 0); |
{ | ||
"name": "@canvas-js/signed-cid", | ||
"version": "0.8.26-alpha.4", | ||
"version": "0.8.26-main-488c798d", | ||
"author": "Canvas Technologies, Inc. (https://canvas.xyz)", | ||
@@ -19,4 +19,4 @@ "type": "module", | ||
"devDependencies": { | ||
"@canvas-js/interfaces": "0.8.26-alpha.4", | ||
"@canvas-js/signed-cid": "0.8.26-alpha.4" | ||
"@canvas-js/interfaces": "0.8.26-main-488c798d", | ||
"@canvas-js/signed-cid": "0.8.26-main-488c798d" | ||
}, | ||
@@ -28,4 +28,5 @@ "dependencies": { | ||
"@noble/hashes": "^1.3.1", | ||
"ethers": "^6.9.1", | ||
"multiformats": "^13.0.1" | ||
} | ||
} |
35902
24
766
6
+ Addedethers@^6.9.1
+ Added@adraffy/ens-normalize@1.10.1(transitive)
+ Added@noble/curves@1.2.0(transitive)
+ Added@noble/hashes@1.3.2(transitive)
+ Added@types/node@22.7.5(transitive)
+ Addedaes-js@4.0.0-beta.5(transitive)
+ Addedethers@6.13.5(transitive)
+ Addedtslib@2.7.0(transitive)
+ Addedundici-types@6.19.8(transitive)
+ Addedws@8.17.1(transitive)