Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@canvas-js/signatures
Advanced tools
Signature utilities for the Canvas data structures.
Each Canvas application is built around a log of signed messages:
|-------------------------| |-------------------------| |-------------------------|
| Message<Session> | | Message<Action> | | Message<Action> |
| topic: ... | | topic: ... | | topic: ... |
| clock: 0 | | clock: 1 | | clock: 2 |
| parents: [Message] | | parents: [Message] | | parents: [Message] |
| payload: | ---> | payload: | ---> | payload: |
| type: "session" | | type: "action" | | type: "action" |
| address: ... | | address: ... | | address: ... |
| publicKey: ... | | name: ... | | name: ... |
| authorizationData: | | args: ... | | args: ... |
| signature: ... | | timestamp: ... | | timestamp: ... |
|-------------------------| |-------------------------| |-------------------------|
|-------------------------| |-------------------------| |-------------------------|
| Signature | | Signature | | Signature |
| codec: ... | | codec: ... | | codec: ... |
| publicKey: ... | | publicKey: ... | | publicKey: ... |
| signature: ... | | signature: ... | | signature: ... |
|-------------------------| |-------------------------| |-------------------------|
Each Message is paired with a Signature that cryptographically authenticates the message as coming from the expected user. To accomplish this, the message is signed by the publicKey on the signature, which you can think of as the user's session key.
To authorize a session key, the first time it is used on the log,
it must be used to sign a Message<Session>
which authorizes itself
(publicKey
) to be used by the user (address
).
This authorization is stored in an AuthorizationData
object
inside the Session, and checked by the signer package(s) provided
to the log, e.g. SIWESigner, Eip712Signer, ATPSigner.
The AuthorizationData can just be a simple { signature }
, but
some wallets or DIDs may use other data to generate a signature.
For example, Sign In With Ethereum expects an issuance time,
expiry time, and URI to generate a sign-in popup.
Under the hood, signer packages use the Ed25519DelegateSigner and
Secp256k1DelegateSigner classes which provide sign()
and verify()
to create/verify session-key signatures for the message.
Session signers also expose newSession()
and verifySession()
methods, to create/verify Sessions and initialize new session keys.
Messages are implemented as a generic class that accepts different Payloads, which may be actions or sessions.
type Message<Payload = unknown> = {
topic: string
clock: number
parents: string[]
payload: Payload
}
Each Message
is stored alongside a Signature
in the log as a
[Message, Signature]
tuple, that includes:
codec
string that identifies how to encode the message to bytes for signingpublicKey
did:key URIsignature
byte array containing the raw signature bytestype Signature = {
codec: "dag-cbor" | "dag-json" | "canvas-action-eip712" | "canvas-session-eip712"
publicKey: string // did:key URI
signature: Uint8Array
}
For ordinary offchain applications, dag-cbor
is used to encode all types
of messages, both Actions and Sessions.
For applications that may need to be verified onchain, canvas-action-eip712
and canvas-session-eip712
codecs are used to encode Actions and Sessions
respectively.
Only Secp256k1 and Ed25519 signature schemes are supported. Each did:key URI identifies its signature scheme using a multicodec varint in addition to encoding its public key.
The four supported codec
values are
dag-cbor
, which canonically encodes the entire message to JSON using the dag-cbor IPLD codecdag-json
, which canonically encodes the entire message to CBOR using the dag-json IPLD codeccanvas-action-eip712
, which encodes Message<Action>
objects to a keccak-256 hash using a fixed EIP-712 schemacanvas-session-eip712
, which encodes Message<Session<Eip712SessionData>>
objects to a keccak-256 hash using a fixed EIP-712 schemaOne important consideration here is that the Ed25519 signature schemes includes a prehash step as a part of the specification, and thus can safely sign byte arrays of any length. Secp256k1 doesn't, and can only sign 32-byte hashes.
What this means is that the dag-cbor
and dag-json
signature codecs can only be used with Ed25519 keypairs, since Secp256k1 doesn't specify a prehash step. Meanwhile, canvas-action-eip712
and canvas-session-eip712
can only be used with Secp256k1 keypairs, since that's already part of the EIP-712 specification.
JSON and CBOR can encode arbitrary objects, so they can be used with for messages with any kind of payload. EIP-712 can only be used with static types, which is why we need separate codecs canvas-action-eip712
and canvas-session-eip712
for actions and sessions. This also means that canvas-session-eip712
can only be used with Eip712Signer
sessions, which have a Eip712SessionData
object as the session authorizationData
.
type Eip712SessionData = {
signature: Uint8Array
}
Once a message has been signed, we need another serialization format to use for storing the signature and message together in the log, for gossiping over libp2p, and for sending over the wire during merkle sync. For these, we use a compact tuple representation encoded with dag-cbor.
export type SignatureTuple = [codec: string, publicKey: string, signature: Uint8Array]
export type MessageTuple = [
signature: SignatureTuple,
topic: string,
clock: number,
parents: Uint8Array[],
payload: unknown,
]
This format is also used to derive message IDs. From the GossipLog documentation:
Message IDs begin with the message clock, encoded as a reverse unsigned varint, followed by the sha2-256 hash of the serialized signed message, and truncated to 20 bytes total. These are encoded using the
base32hex
alphabet to get 32-character string IDs, like054ki1oubq8airsc9d8sbg0t7itqbdlf
.
The hash is the sha2-256 of the cbor-encoded message tuple.
GossipLog uses the Signer
interface to manage signing and verifying messages.
interface Signer<Payload = unknown> {
uri: string // did:key URI
codecs: string[]
sign(message: Message<Payload>, options?: { codec?: string }): Awaitable<Signature>
verify(signature: Signature, message: Message<Payload>): Awaitable<void>
export(): { type: string; privateKey: Uint8Array }
}
The primary signer implementation is Ed25519Delegate
, exported here in @canvas-js/signatures
. It uses the Ed25519 signature scheme and supports both dag-json
and dag-cbor
signature codecs.
// create a new random keypair
const signer = new Ed25519Delegate()
console.log(signer.codecs) // ["dag-cbor", "dag-json"]
console.log(signer.uri) // "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
// import an existing private key
const signer = new Ed25519Delegate({ type: "ed25519", privateKey: new Uint8Array([ ... ])})
Every GossipLog instance has one "primary" signer it uses to sign new messages by default; if one is not provided in the initial config object then a random Ed25519Delegate
is created. This primary signer is also used to verify incoming messages.
These behaviors can be overriden by providing a verifySignature: (signature: Signature, message: Message<Payload>) => Awaitable<void>
function in the initial GossipLog config object, and passing an explicit signer in the options argument of the append
method.
GossipLog and the Signer
interface are designed to be relatively generic; "actions" and "sessions" are specific to Canvas apps.
Canvas apps use signers implementing the SessionSigner
interface:
interface SessionSigner<AuthorizationData = any> {
codecs: string[]
key: string
match: (address: string) => boolean
verify: (signature: Signature, message: Message<Action | Session<AuthorizationData>>) => Awaitable<void>
verifySession: (topic: string, session: Session<AuthorizationData>) => Awaitable<void>
sign(message: Message<Action | Session<AuthorizationData>>, options?: { codec?: string }): Awaitable<Signature>
getSession: (
topic: string,
options?: { timestamp?: number; fromCache?: boolean },
) => Awaitable<Session<AuthorizationData>>
clear(topic: string): Awaitable<void>
}
This looks complicated but it essentially extends a Signer<Action | Session<AuthorizationData>>
interface with methods to authorize and verify sessions.
FAQs
Signature utilities for the Canvas data structures.
We found that @canvas-js/signatures demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.