├── packages ├── api │ ├── .gitignore │ ├── src │ │ ├── jwt │ │ │ └── index.ts │ │ ├── axios │ │ │ ├── index.ts │ │ │ ├── config.ts │ │ │ └── axios.ts │ │ ├── helpers │ │ │ ├── errors.ts │ │ │ ├── address.ts │ │ │ ├── index.ts │ │ │ ├── encoding.ts │ │ │ ├── apiKeyToCryptoKey.ts │ │ │ └── cryptoKeyToApiKey.ts │ │ └── index.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── Readme.md │ └── package.json ├── signatures │ ├── .gitignore │ ├── src │ │ ├── publickey │ │ │ ├── index.ts │ │ │ └── ethereum.ts │ │ ├── keyreg │ │ │ ├── index.ts │ │ │ ├── __snapshots__ │ │ │ │ └── message.test.ts.snap │ │ │ ├── message.ts │ │ │ └── params.ts │ │ ├── __snapshots__ │ │ │ └── mailer.test.ts.snap │ │ ├── consts.ts │ │ ├── index.ts │ │ ├── mailchain_message_confirmation.ts │ │ ├── raw_ed25519.ts │ │ ├── verify.ts │ │ ├── mailchain_username.ts │ │ ├── eth_personal.ts │ │ ├── mailchain_password_reset.ts │ │ ├── errors.ts │ │ └── raw_ed25519.test.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── Readme.md │ └── package.json ├── internal │ ├── .gitignore │ ├── src │ │ ├── errors │ │ │ ├── index.ts │ │ │ ├── validation.ts │ │ │ └── unexpected.ts │ │ ├── metadata │ │ │ └── index.ts │ │ ├── receiving │ │ │ ├── mail │ │ │ │ └── index.ts │ │ │ ├── mailer │ │ │ │ ├── index.ts │ │ │ │ └── author.ts │ │ │ ├── index.ts │ │ │ ├── payload │ │ │ │ └── index.ts │ │ │ └── deliveryRequests │ │ │ │ └── index.ts │ │ ├── transport │ │ │ ├── mail │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── verifier │ │ │ │ ├── index.ts │ │ │ │ └── sender.ts │ │ │ ├── distribution │ │ │ │ ├── index.ts │ │ │ │ └── distribution.ts │ │ │ ├── deliveryRequests │ │ │ │ ├── index.ts │ │ │ │ ├── delivery.ts │ │ │ │ ├── keybundle.ts │ │ │ │ └── envelope.ts │ │ │ ├── mailer │ │ │ │ ├── index.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── content.test.ts.snap │ │ │ │ ├── types.ts │ │ │ │ └── mailerProof.test.ts │ │ │ ├── serialization │ │ │ │ ├── payload.ts │ │ │ │ ├── index.ts │ │ │ │ ├── chunk.ts │ │ │ │ └── chunk.test.ts │ │ │ ├── index.ts │ │ │ └── payload │ │ │ │ ├── index.ts │ │ │ │ ├── verifier.ts │ │ │ │ ├── payload.ts │ │ │ │ ├── verifier.test.ts │ │ │ │ └── headers.ts │ │ ├── identityKeys │ │ │ └── index.ts │ │ ├── nameservices │ │ │ └── index.ts │ │ ├── mailbox │ │ │ ├── payloadStorage │ │ │ │ ├── index.ts │ │ │ │ └── payloadStorage.ts │ │ │ ├── index.ts │ │ │ ├── userMailboxHasher.ts │ │ │ ├── messageId.ts │ │ │ ├── types.ts │ │ │ ├── userMailboxHasher.test.ts │ │ │ ├── messageCrypto.ts │ │ │ └── messageCrypto.test.ts │ │ ├── verifiableCredentials │ │ │ ├── request │ │ │ │ └── index.ts │ │ │ ├── jwt.ts │ │ │ ├── verifiableMailchainAddressOwner │ │ │ │ └── index.ts │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── subject.ts │ │ │ ├── parseVerifiablePresentationRequest.ts │ │ │ ├── termsOfUse.ts │ │ │ ├── presentation.test.ts │ │ │ ├── payload.test.ts │ │ │ ├── __snapshots__ │ │ │ │ ├── presentation.test.ts.snap │ │ │ │ ├── resolver.test.ts.snap │ │ │ │ └── payload.test.ts.snap │ │ │ ├── parseVerifiablePresentationRequest.test.ts │ │ │ ├── presentation.ts │ │ │ ├── payload.ts │ │ │ ├── resolver.test.ts │ │ │ └── did.ts │ │ ├── sending │ │ │ ├── deliveryRequests │ │ │ │ └── index.ts │ │ │ ├── verifiablePresentationRequest │ │ │ │ ├── index.ts │ │ │ │ └── payload.ts │ │ │ ├── payload │ │ │ │ ├── index.ts │ │ │ │ ├── create.test.ts │ │ │ │ └── create.ts │ │ │ ├── index.ts │ │ │ ├── distributor │ │ │ │ └── index.ts │ │ │ ├── mail │ │ │ │ ├── index.ts │ │ │ │ └── payloads.ts │ │ │ └── errors.ts │ │ ├── formatters │ │ │ ├── consts.ts │ │ │ ├── parse.test.ts │ │ │ ├── generate.test.ts │ │ │ ├── simpleMimeHeaderParser.ts │ │ │ └── __snapshots__ │ │ │ │ └── parse.test.ts.snap │ │ ├── messageSync │ │ │ └── index.ts │ │ ├── user │ │ │ ├── index.ts │ │ │ ├── createAlias.ts │ │ │ ├── utils.ts │ │ │ ├── consolidateMailbox.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── messagingKeys │ │ │ ├── index.ts │ │ │ └── contractResolvers │ │ │ │ ├── errors.ts │ │ │ │ └── resolver.ts │ │ ├── configuration.ts │ │ ├── mailboxRuleEngine │ │ │ ├── index.ts │ │ │ ├── rule.ts │ │ │ └── actions.ts │ │ ├── mailchainResult.ts │ │ ├── migration.test.ts │ │ └── migration.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── rollup.const.js │ ├── Readme.md │ └── package.json ├── message-composer │ ├── src │ │ ├── __tests__ │ │ │ ├── .gitignore │ │ │ └── mailchain-logo.png │ │ ├── index.ts │ │ ├── consts.ts │ │ ├── hasOnlyAscii.ts │ │ ├── headerFactories.ts │ │ ├── messageComposerContext.ts │ │ ├── headerOrder.ts │ │ └── fallback.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── package.json │ └── Readme.md ├── crypto │ ├── src │ │ ├── cipher │ │ │ ├── ecdh │ │ │ │ ├── index.ts │ │ │ │ ├── ecdh.test.ts │ │ │ │ ├── ecdh.ts │ │ │ │ └── ed25519.ts │ │ │ ├── nacl │ │ │ │ ├── index.ts │ │ │ │ ├── publicKeyDecrypter.ts │ │ │ │ ├── publicKeyEncrypter.ts │ │ │ │ ├── publicKeyEndToEnd.test.ts │ │ │ │ ├── privateKeyDecrypter.ts │ │ │ │ ├── privateKeyEncrypter.ts │ │ │ │ └── secretbox.ts │ │ │ ├── index.ts │ │ │ ├── keyExchange.ts │ │ │ └── cipher.ts │ │ ├── scrypt │ │ │ ├── index.ts │ │ │ └── scrypt.ts │ │ ├── testing │ │ │ ├── index.ts │ │ │ ├── public.ts │ │ │ └── private.ts │ │ ├── multikey │ │ │ ├── index.ts │ │ │ ├── names.ts │ │ │ └── bytes.ts │ │ ├── secp256k1 │ │ │ ├── index.ts │ │ │ └── private.ts │ │ ├── secp256r1 │ │ │ ├── index.ts │ │ │ ├── public.ts │ │ │ └── private.ts │ │ ├── errors.ts │ │ ├── rand.ts │ │ ├── ed25519 │ │ │ ├── index.ts │ │ │ ├── hd.ts │ │ │ ├── public.ts │ │ │ ├── derivation.ts │ │ │ └── hd.test.ts │ │ ├── private.ts │ │ ├── rand.test.const.ts │ │ ├── index.ts │ │ ├── public.ts │ │ ├── mnemonic │ │ │ └── mnemonic.ts │ │ └── keys.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── Readme.md │ └── package.json ├── keyring │ ├── .eslintignore │ ├── src │ │ ├── index.ts │ │ ├── test.const.ts │ │ ├── constants.ts │ │ └── functions.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── Readme.md │ └── package.json ├── tsconfig.json ├── sdk │ ├── rollup.const.js │ ├── babel.config.cjs │ ├── jest.config.cjs │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ └── internal │ │ │ ├── privateMessagingKey.ts │ │ │ ├── addressNonce.ts │ │ │ ├── resolvers.ts │ │ │ ├── index.ts │ │ │ ├── keys.test.ts │ │ │ └── keys.ts │ ├── jest.setup.js │ └── package.json ├── addressing │ ├── src │ │ ├── protocols │ │ │ ├── index.ts │ │ │ ├── filecoin │ │ │ │ ├── types.ts │ │ │ │ ├── address.ts │ │ │ │ └── const.ts │ │ │ ├── errors.ts │ │ │ ├── solana │ │ │ │ ├── test.const.ts │ │ │ │ └── address.ts │ │ │ ├── ethereum │ │ │ │ ├── test.const.ts │ │ │ │ ├── address.ts │ │ │ │ └── address.test.ts │ │ │ ├── near │ │ │ │ └── address.ts │ │ │ ├── consts.ts │ │ │ └── tezos │ │ │ │ ├── test.const.ts │ │ │ │ └── const.ts │ │ ├── nameservices │ │ │ ├── index.ts │ │ │ └── matchesNameservice.ts │ │ ├── formatMailLike.ts │ │ ├── test.constants.ts │ │ ├── index.ts │ │ ├── errors.ts │ │ ├── parseWalletAddress.ts │ │ ├── addressCasing.ts │ │ ├── walletAddress.ts │ │ ├── addressFromPublicKey.test.ts │ │ ├── addressFormatting.ts │ │ ├── addressFromPublicKey.ts │ │ ├── walletAddress.test.ts │ │ └── addressCasing.test.ts │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── Readme.md │ └── package.json └── encoding │ ├── babel.config.cjs │ ├── tsconfig.json │ ├── jest.config.cjs │ ├── src │ ├── utf8.ts │ ├── ensure.ts │ ├── index.ts │ ├── base58.ts │ ├── ascii.ts │ ├── base64.test.ts │ ├── consts.ts │ ├── ascii.test.ts │ ├── base64.ts │ ├── base32.ts │ ├── base58Check.ts │ ├── utf8.test.ts │ ├── base32.test.ts │ ├── hex.test.ts │ └── hex.ts │ ├── Readme.md │ └── package.json ├── tsconfig.json ├── .depcheckrc └── .github └── workflows └── sync.yml /packages/api/.gitignore: -------------------------------------------------------------------------------- 1 | src/api 2 | -------------------------------------------------------------------------------- /packages/signatures/.gitignore: -------------------------------------------------------------------------------- 1 | src/api 2 | -------------------------------------------------------------------------------- /packages/internal/.gitignore: -------------------------------------------------------------------------------- 1 | src/protobuf 2 | -------------------------------------------------------------------------------- /packages/api/src/jwt/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jwt'; 2 | -------------------------------------------------------------------------------- /packages/message-composer/src/__tests__/.gitignore: -------------------------------------------------------------------------------- 1 | *.eml 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/ecdh/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ecdh'; 2 | -------------------------------------------------------------------------------- /packages/crypto/src/scrypt/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scrypt'; 2 | -------------------------------------------------------------------------------- /.depcheckrc: -------------------------------------------------------------------------------- 1 | ignores: ['protobufjs', 'long', 'jest-environment-jsdom'] 2 | -------------------------------------------------------------------------------- /packages/internal/src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './unexpected'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/metadata/index.ts: -------------------------------------------------------------------------------- 1 | export * from './metadata'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/mail/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mail'; 2 | -------------------------------------------------------------------------------- /packages/keyring/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | **/generated/** 3 | generated/* -------------------------------------------------------------------------------- /packages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/mailer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mailer'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mail/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | -------------------------------------------------------------------------------- /packages/signatures/src/publickey/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ethereum'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/identityKeys/index.ts: -------------------------------------------------------------------------------- 1 | export * from './identityKeys'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/nameservices/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nameservices'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/index.ts: -------------------------------------------------------------------------------- 1 | export { MailReceiver } from './mail'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/payload/index.ts: -------------------------------------------------------------------------------- 1 | export * from './payload'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/transport/verifier/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sender'; 2 | -------------------------------------------------------------------------------- /packages/api/src/axios/index.ts: -------------------------------------------------------------------------------- 1 | export * from './axios'; 2 | export * from './config'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/payloadStorage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './payloadStorage'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/transport/distribution/index.ts: -------------------------------------------------------------------------------- 1 | export * from './distribution'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/request/index.ts: -------------------------------------------------------------------------------- 1 | export * from './request'; 2 | -------------------------------------------------------------------------------- /packages/keyring/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keyring'; 2 | export * from './functions'; 3 | -------------------------------------------------------------------------------- /packages/sdk/rollup.const.js: -------------------------------------------------------------------------------- 1 | export const explicitExports = ['@mailchain/sdk/internal/']; 2 | -------------------------------------------------------------------------------- /packages/crypto/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './private'; 2 | export * from './public'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/deliveryRequests/index.ts: -------------------------------------------------------------------------------- 1 | export * from './deliveryRequests'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/sending/deliveryRequests/index.ts: -------------------------------------------------------------------------------- 1 | export * from './deliveryRequests'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/sending/verifiablePresentationRequest/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sender'; 2 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/index.ts: -------------------------------------------------------------------------------- 1 | export * from './consts'; 2 | export * from './errors'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/jwt.ts: -------------------------------------------------------------------------------- 1 | export type VerifiablePresentationJWT = string; 2 | -------------------------------------------------------------------------------- /packages/internal/src/formatters/consts.ts: -------------------------------------------------------------------------------- 1 | export const X_IDENTITY_KEYS = 'X-IdentityKeys' as const; 2 | -------------------------------------------------------------------------------- /packages/internal/src/sending/payload/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create'; 2 | export * from './store'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/transport/deliveryRequests/index.ts: -------------------------------------------------------------------------------- 1 | export { createDelivery } from './delivery'; 2 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/verifiableMailchainAddressOwner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './verifier'; 2 | -------------------------------------------------------------------------------- /packages/crypto/src/multikey/index.ts: -------------------------------------------------------------------------------- 1 | export * from './names'; 2 | export * from './bytes'; 3 | export * from './ids'; 4 | -------------------------------------------------------------------------------- /packages/internal/src/messageSync/index.ts: -------------------------------------------------------------------------------- 1 | export * from './messageSync'; 2 | export * from './previousMessageSync'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/sending/index.ts: -------------------------------------------------------------------------------- 1 | export { PreflightCheckError } from './errors'; 2 | export * from './distributor'; 3 | -------------------------------------------------------------------------------- /packages/message-composer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './consts'; 2 | export { createMessageComposer } from './messageComposer'; 3 | -------------------------------------------------------------------------------- /packages/crypto/src/secp256k1/index.ts: -------------------------------------------------------------------------------- 1 | export { SECP256K1PrivateKey } from './private'; 2 | export { SECP256K1PublicKey } from './public'; 3 | -------------------------------------------------------------------------------- /packages/crypto/src/secp256r1/index.ts: -------------------------------------------------------------------------------- 1 | export { SECP256R1PrivateKey } from './private'; 2 | export { SECP256R1PublicKey } from './public'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/context.ts: -------------------------------------------------------------------------------- 1 | export const W3C_CREDENTIALS_CONTEXT = 'https://www.w3.org/2018/credentials/v1'; 2 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/filecoin/types.ts: -------------------------------------------------------------------------------- 1 | export enum FilecoinAddressType { 2 | SECP256K1 = 1, 3 | BLS12_381 = 3, 4 | DELEGATED = 4, 5 | } 6 | -------------------------------------------------------------------------------- /packages/api/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/crypto/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/internal/src/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './userProfile'; 2 | export * from './types'; 3 | export * from './createAlias'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /packages/sdk/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/addressing/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/encoding/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/internal/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/internal/src/sending/distributor/index.ts: -------------------------------------------------------------------------------- 1 | export type { SentPayloadDistributionRequests } from './deliveryRequests'; 2 | export * from './distributor'; 3 | -------------------------------------------------------------------------------- /packages/keyring/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/signatures/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/addressing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/crypto/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class ErrorUnsupportedKey extends Error { 2 | constructor(curve: string) { 3 | super(`${curve} is not supported`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/encoding/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mailer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './content'; 2 | export * from './mailerProof'; 3 | export * from './payload'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/internal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyring/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/message-composer/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/signatures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/api/src/helpers/errors.ts: -------------------------------------------------------------------------------- 1 | export class ErrorUnsupportedKey extends Error { 2 | constructor(curve: string) { 3 | super(`${curve} is not supported`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/message-composer/src/__tests__/mailchain-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailchain/mailchain-sdk-js/main/packages/message-composer/src/__tests__/mailchain-logo.png -------------------------------------------------------------------------------- /packages/message-composer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/addressing/src/nameservices/index.ts: -------------------------------------------------------------------------------- 1 | export { NAMESERVICE_DESCRIPTIONS } from './nameserviceDescriptions'; 2 | export { matchesNameservice } from './matchesNameservice'; 3 | -------------------------------------------------------------------------------- /packages/internal/src/sending/mail/index.ts: -------------------------------------------------------------------------------- 1 | export * from './prepare'; 2 | export * from './sender'; 3 | export type { Address, Attachment, SendMailParams } from './sendMailParams'; 4 | -------------------------------------------------------------------------------- /packages/crypto/src/rand.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from '@noble/hashes/utils'; 2 | 3 | export type RandomFunction = (len?: number) => Uint8Array; 4 | export const secureRandom = randomBytes; 5 | -------------------------------------------------------------------------------- /packages/internal/src/transport/distribution/distribution.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from '../payload'; 2 | 3 | export type Distribution = { 4 | recipients: string[]; 5 | payload: Payload; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/index.ts: -------------------------------------------------------------------------------- 1 | export * from './publicKeyEncrypter'; 2 | export * from './publicKeyDecrypter'; 3 | export * from './privateKeyEncrypter'; 4 | export * from './privateKeyDecrypter'; 5 | -------------------------------------------------------------------------------- /packages/internal/src/errors/validation.ts: -------------------------------------------------------------------------------- 1 | export class ValidationError extends Error { 2 | constructor(message: string, errorOptions?: { cause?: Error }) { 3 | super(message, errorOptions); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/index.ts: -------------------------------------------------------------------------------- 1 | export { ED25519KeyExchange } from './ecdh/ed25519'; 2 | 3 | export * from './cipher'; 4 | export * from './keyExchange'; 5 | export * from './nacl'; 6 | export * from './ecdh'; 7 | -------------------------------------------------------------------------------- /packages/internal/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { Configuration } from './configuration'; 2 | export type { MailchainResult } from './mailchainResult'; 3 | export { partitionMailchainResults } from './mailchainResult'; 4 | -------------------------------------------------------------------------------- /packages/internal/src/transport/serialization/payload.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encrypted payload. 3 | */ 4 | export interface EncryptedPayload { 5 | EncryptedHeaders: Buffer; 6 | 7 | EncryptedContentChunks: Buffer[]; 8 | } 9 | -------------------------------------------------------------------------------- /packages/signatures/src/keyreg/index.ts: -------------------------------------------------------------------------------- 1 | export { createProofMessage } from './message'; 2 | export { getLatestProofParams, getMailchainUsernameParams } from './params'; 3 | export type { ProofParams } from './params'; 4 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/index.ts: -------------------------------------------------------------------------------- 1 | export * from './request'; 2 | export * from './payload'; 3 | export * from './verifiableMailchainAddressOwner'; 4 | export * from './parseVerifiablePresentationRequest'; 5 | -------------------------------------------------------------------------------- /packages/addressing/src/formatMailLike.ts: -------------------------------------------------------------------------------- 1 | export function formatMailLike(username: string, ...otherParts: string[]): string { 2 | if (otherParts.length === 0) return username; 3 | return `${username}@${otherParts.join('.')}`; 4 | } 5 | -------------------------------------------------------------------------------- /packages/api/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/crypto/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/internal/src/transport/index.ts: -------------------------------------------------------------------------------- 1 | export * from './deliveryRequests'; 2 | export * from './distribution'; 3 | export * from './mail'; 4 | export * from './mailer'; 5 | export * from './payload'; 6 | export * from './verifier'; 7 | -------------------------------------------------------------------------------- /packages/keyring/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/sdk/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/addressing/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/encoding/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/internal/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/signatures/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/message-composer/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { createConfig } = require('../../../../jest.config.cjs'); 3 | 4 | module.exports = { 5 | ...createConfig(path.join(__dirname, '..', '..', '..', '..')), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/internal/rollup.const.js: -------------------------------------------------------------------------------- 1 | export const explicitExports = [ 2 | '@mailchain/internal/transport/mailer/mailerProof', 3 | '@mailchain/internal/sending/verifiablePresentationRequest', 4 | '@mailchain/internal/verifiableCredentials', 5 | ]; 6 | -------------------------------------------------------------------------------- /packages/sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "allowJs": true 6 | }, 7 | "include": ["src/**/*"], 8 | "typeRoots": ["./src/internal/protobuf/"] 9 | 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/src/helpers/address.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '@mailchain/encoding'; 2 | import { Address } from '../api'; 3 | 4 | export const getAddressFromApiResponse = (address: Address) => { 5 | return decode(address.encoding, address.value); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/index.ts: -------------------------------------------------------------------------------- 1 | export { MailchainMailboxOperations } from './mailboxOperations'; 2 | export type { MailboxOperations } from './mailboxOperations'; 3 | export { MailboxStorage } from './mailboxStorage'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/internal/src/messagingKeys/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addressNonce'; 2 | export * from './errors'; 3 | export * from './messagingKeys'; 4 | export * from './messagingKeys'; 5 | export * from './privateMessagingKeys'; 6 | export * from './proof'; 7 | -------------------------------------------------------------------------------- /packages/api/src/axios/config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration as APIConfiguration } from '../api/configuration'; 2 | 3 | export function createAxiosConfiguration(apiPath: string): APIConfiguration { 4 | return new APIConfiguration({ 5 | basePath: apiPath, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /packages/encoding/src/utf8.ts: -------------------------------------------------------------------------------- 1 | export function encodeUtf8(input: Uint8Array): string { 2 | return Buffer.from(input).toString('utf-8'); 3 | } 4 | 5 | export function decodeUtf8(input: string): Uint8Array { 6 | return new Uint8Array(Buffer.from(input, 'utf-8')); 7 | } 8 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/keyExchange.ts: -------------------------------------------------------------------------------- 1 | import { PrivateKey, PublicKey } from '..'; 2 | 3 | export interface KeyExchange { 4 | EphemeralKey: () => Promise; 5 | 6 | SharedSecret: (privateKey: PrivateKey, publicKey: PublicKey) => Promise; 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/src/transport/payload/index.ts: -------------------------------------------------------------------------------- 1 | export type { Payload } from './payload'; 2 | export { serializeAndEncryptPayload } from './payload'; 3 | export type { ContentType } from './headers'; 4 | export { SerializablePayloadHeadersImpl } from './headersSerialize'; 5 | -------------------------------------------------------------------------------- /packages/sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { Configuration } from '@mailchain/internal'; 2 | export { Mailchain } from './mailchain'; 3 | export type { SentMail } from './mailchain'; 4 | export type { Address, Attachment, SendMailParams } from '@mailchain/internal/sending/mail'; 5 | -------------------------------------------------------------------------------- /packages/api/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './jwt'; 3 | export * from './axios'; 4 | export { createAxiosConfiguration } from './axios/config'; 5 | export { ApiKeyConvert, CryptoKeyConvert, encodingTypeToEncodingEnum, getAddressFromApiResponse } from './helpers'; 6 | -------------------------------------------------------------------------------- /packages/crypto/src/ed25519/index.ts: -------------------------------------------------------------------------------- 1 | export { ED25519PrivateKey } from './private'; 2 | export { ED25519PublicKey, ED25519PublicKeyLen } from './public'; 3 | export { ED25519ExtendedPrivateKey } from './hd'; 4 | export { ed25519DeriveHardenedKey as deriveHardenedKey } from './derivation'; 5 | -------------------------------------------------------------------------------- /packages/internal/src/transport/serialization/index.ts: -------------------------------------------------------------------------------- 1 | export { CHUNK_LENGTH_1MB } from './chunk'; 2 | export { decryptPayload } from './decrypt'; 3 | export { encryptPayload } from './encrypt'; 4 | export { deserialize, serialize } from './serialization'; 5 | export * from './headers'; 6 | -------------------------------------------------------------------------------- /packages/signatures/src/__snapshots__/mailer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createMailerProofParamsForSigning() version-1.0 1`] = `"{"authorContentSignature":"05060708","expires":1654473600,"mailerMessagingKey":"e22e322f8740c60172111ac8eadcdda2512f90d06d0e503ef189979a159bece1e8"}"`; 4 | -------------------------------------------------------------------------------- /packages/encoding/src/ensure.ts: -------------------------------------------------------------------------------- 1 | import { EncodingType } from './consts'; 2 | import { decode } from './encoding'; 3 | 4 | export function ensureDecoded(value: Uint8Array | string | Buffer, encoding: EncodingType) { 5 | if (typeof value === 'string') { 6 | return decode(encoding, value); 7 | } 8 | 9 | return value; 10 | } 11 | -------------------------------------------------------------------------------- /packages/encoding/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './consts'; 2 | export * from './ensure'; 3 | export * from './encoding'; 4 | export * from './hex'; 5 | export * from './base32'; 6 | export * from './base58'; 7 | export * from './base58Check'; 8 | export * from './base64'; 9 | export * from './utf8'; 10 | export * from './ascii'; 11 | -------------------------------------------------------------------------------- /packages/keyring/src/test.const.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PrivateKey, BobED25519PrivateKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { KeyRing } from './keyring'; 3 | 4 | export const aliceKeyRing = KeyRing.fromPrivateKey(AliceED25519PrivateKey); 5 | export const bobKeyRing = KeyRing.fromPrivateKey(BobED25519PrivateKey); 6 | -------------------------------------------------------------------------------- /packages/internal/src/sending/errors.ts: -------------------------------------------------------------------------------- 1 | export class PreflightCheckError extends Error { 2 | readonly type = 'preflight_check_error'; 3 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#preflight_check_failed'; 4 | constructor(message: string) { 5 | super(`${message}. Try again after fixing the problem.`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/src/errors/unexpected.ts: -------------------------------------------------------------------------------- 1 | export class UnexpectedMailchainError extends Error { 2 | readonly type = 'unexpected_error'; 3 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#unexpected_error'; 4 | constructor(message: string, errorOptions?: { cause?: Error }) { 5 | super(message, errorOptions); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/src/configuration.ts: -------------------------------------------------------------------------------- 1 | export type Configuration = { 2 | apiPath: string; 3 | mailchainAddressDomain: string; 4 | nearRpcUrl: string; 5 | }; 6 | 7 | export const defaultConfiguration: Configuration = { 8 | apiPath: 'https://api.mailchain.com', 9 | mailchainAddressDomain: 'mailchain.com', 10 | nearRpcUrl: 'https://rpc.near.org', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/errors.ts: -------------------------------------------------------------------------------- 1 | export class ProtocolNotSupportedError extends Error { 2 | readonly type = 'protocol_unsupported'; 3 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#protocol_unsupported'; 4 | constructor(public readonly protocol: string) { 5 | super(`[${protocol}] is an unsupported protocol. Try again with a different protocol.`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/sdk/jest.setup.js: -------------------------------------------------------------------------------- 1 | import { TextEncoder, TextDecoder } from 'util'; 2 | import { Crypto } from '@peculiar/webcrypto'; 3 | 4 | global.TextEncoder = TextEncoder; 5 | global.TextDecoder = TextDecoder; 6 | // Setup from opaque-ts https://github.com/cloudflare/opaque-ts/blob/main/test/jest.setup-file.mjs 7 | if (typeof crypto === 'undefined') { 8 | global.crypto = new Crypto(); 9 | } 10 | -------------------------------------------------------------------------------- /packages/signatures/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const KindEthereumPersonalMessage = 'ethereum_personal_message'; 2 | // KindRawED25519 signs the message with the curve's native sign method with no pre-processing. 3 | export const KindRawED25519 = 'raw_ed25519'; 4 | export const KindTezos = 'tezos_signed_message_micheline'; 5 | export const KindMailchainUsernameIdentityKey = 'mailchain_username_identity_key'; 6 | -------------------------------------------------------------------------------- /packages/crypto/src/private.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from './public'; 2 | 3 | export interface PrivateKey extends SignerWithPublicKey { 4 | readonly bytes: Uint8Array; 5 | } 6 | 7 | export interface SignerWithPublicKey extends Signer { 8 | readonly publicKey: PublicKey; 9 | } 10 | 11 | export interface Signer { 12 | sign(message: Uint8Array): Promise; 13 | curve: string; 14 | } 15 | -------------------------------------------------------------------------------- /packages/signatures/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './errors'; 2 | export * from './eth_personal'; 3 | export * from './mailchain_message_confirmation'; 4 | export * from './mailchain_msgkey'; 5 | export * from './mailchain_password_reset'; 6 | export * from './mailchain_username'; 7 | export * from './raw_ed25519'; 8 | export * from './verify'; 9 | export * from './mailer'; 10 | export * from './consts'; 11 | -------------------------------------------------------------------------------- /packages/crypto/src/rand.test.const.ts: -------------------------------------------------------------------------------- 1 | export function testRandomFunction(num?: number): Uint8Array { 2 | return new Uint8Array([ 3 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 4 | 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 5 | 28, 29, 30, 31, 6 | ]).slice(0, num || 32); 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/src/mailboxRuleEngine/index.ts: -------------------------------------------------------------------------------- 1 | export { MailchainUserBlocklistRule } from './mailchainUserBlocklist'; 2 | export { MailchainRuleRepository } from './mailchainRuleRepository'; 3 | export { MailboxRuleEngine } from './mailboxRuleEngine'; 4 | export type { MailboxRule, MailboxRuleAction, MailboxRuleCondition } from './rule'; 5 | export type { RuleApplyParams, RulesSource } from './mailboxRuleEngine'; 6 | -------------------------------------------------------------------------------- /packages/internal/src/user/createAlias.ts: -------------------------------------------------------------------------------- 1 | import { MailchainAddress } from '@mailchain/addressing'; 2 | import { Alias } from './types'; 3 | 4 | export function createMailboxAlias(address: MailchainAddress, params?: Partial>): Alias { 5 | return { 6 | address, 7 | allowSending: params?.allowSending ?? true, 8 | allowReceiving: params?.allowReceiving ?? true, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/addressing/src/test.constants.ts: -------------------------------------------------------------------------------- 1 | export const EthereumAlice = new Uint8Array([ 2 | 0xd5, 0xab, 0x4c, 0xe3, 0x60, 0x5c, 0xd5, 0x90, 0xdb, 0x60, 0x9b, 0x6b, 0x5c, 0x89, 0x1, 0xfd, 0xb2, 0xef, 0x7f, 3 | 0xe6, 4 | ]); 5 | 6 | export const EthereumBob = new Uint8Array([ 7 | 0x92, 0xd8, 0xf1, 0x2, 0x48, 0xc6, 0xa3, 0x95, 0x3c, 0xc3, 0x69, 0x2a, 0x89, 0x46, 0x55, 0xad, 0x5, 0xd6, 0x1e, 8 | 0xfb, 9 | ]); 10 | -------------------------------------------------------------------------------- /packages/internal/src/messagingKeys/contractResolvers/errors.ts: -------------------------------------------------------------------------------- 1 | export class MessagingKeyNotFoundInContractError extends Error { 2 | constructor() { 3 | super(`Messaging key not found in contract.`); 4 | } 5 | } 6 | 7 | export class InvalidContractResponseError extends Error { 8 | constructor(public readonly problem: string) { 9 | super(`Messaging key contract returned invalid response. ${problem}}`); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/encoding/src/base58.ts: -------------------------------------------------------------------------------- 1 | import { base58 } from '@scure/base'; 2 | export function decodeBase58(input: string): Uint8Array { 3 | return base58.decode(input); 4 | } 5 | 6 | export function encodeBase58(input: Uint8Array): string { 7 | return base58.encode(input); 8 | } 9 | 10 | export function isBase58(input: string): boolean { 11 | return /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/.test(input); 12 | } 13 | -------------------------------------------------------------------------------- /packages/crypto/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ED25519ExtendedPrivateKey, ED25519PrivateKey, ED25519PublicKey, deriveHardenedKey } from './ed25519'; 2 | export { SECP256K1PrivateKey, SECP256K1PublicKey } from './secp256k1'; 3 | export * from './keys'; 4 | export * from './multikey'; 5 | export * from './public'; 6 | export * from './private'; 7 | export * from './rand'; 8 | export * from './hd'; 9 | export * from './cipher'; 10 | export * from './errors'; 11 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mail/types.ts: -------------------------------------------------------------------------------- 1 | export type MailAddress = { 2 | name: string; 3 | address: string; 4 | }; 5 | 6 | export type MailData = { 7 | id: string; 8 | subject: string; 9 | from: MailAddress; 10 | replyTo?: MailAddress; 11 | date: Date; 12 | recipients: MailAddress[]; 13 | carbonCopyRecipients: MailAddress[]; 14 | blindCarbonCopyRecipients: MailAddress[]; 15 | message: string; 16 | plainTextMessage: string; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/internal/src/user/utils.ts: -------------------------------------------------------------------------------- 1 | import { isPublicKey, PublicKey, publicKeyToBytes } from '@mailchain/crypto'; 2 | import { encodeHex } from '@mailchain/encoding'; 3 | import { UserMailbox } from './types'; 4 | 5 | export function encodeMailbox(mailbox: UserMailbox | PublicKey): string { 6 | let key: PublicKey; 7 | if (isPublicKey(mailbox)) key = mailbox; 8 | else key = mailbox.identityKey; 9 | 10 | return encodeHex(publicKeyToBytes(key)); 11 | } 12 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/solana/test.const.ts: -------------------------------------------------------------------------------- 1 | import { decodeBase58 } from '@mailchain/encoding'; 2 | 3 | export const BobSolanaPublicAddressStr = '47L935B9UmtJ9oeF8Xy7AE1qGnxEPnk42eMtwoeVnsx3'; 4 | export const BobSolanaPublicAddress = decodeBase58(BobSolanaPublicAddressStr); 5 | 6 | export const AliceSolanaPublicAddressStr = '8gw8X5R9c8BvmAR83Z72GANcmkfskXATP3DeyxZkk2U8'; 7 | export const AliceSolanaPublicAddress = decodeBase58(AliceSolanaPublicAddressStr); 8 | -------------------------------------------------------------------------------- /packages/crypto/src/testing/public.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '../public'; 2 | 3 | export class UnknownPublicKey implements PublicKey { 4 | readonly bytes: Uint8Array; 5 | readonly curve: string = 'testcurve'; 6 | 7 | constructor() { 8 | this.bytes = new Uint8Array(); 9 | } 10 | 11 | async verify(_message: Uint8Array, _sig: Uint8Array): Promise { 12 | return false; 13 | } 14 | } 15 | 16 | export const AliceUnknownPublicKey = new UnknownPublicKey(); 17 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/ethereum/test.const.ts: -------------------------------------------------------------------------------- 1 | import { decodeHexZeroX } from '@mailchain/encoding'; 2 | 3 | export const BobSECP256K1PublicAddressStr = '0x92d8f10248c6a3953cc3692a894655ad05d61efb'; 4 | export const BobSECP256K1PublicAddress = decodeHexZeroX(BobSECP256K1PublicAddressStr); 5 | 6 | export const AliceSECP256K1PublicAddressStr = '0xD5ab4CE3605Cd590Db609b6b5C8901fdB2ef7FE6'; 7 | export const AliceSECP256K1PublicAddress = decodeHexZeroX(AliceSECP256K1PublicAddressStr); 8 | -------------------------------------------------------------------------------- /packages/encoding/src/ascii.ts: -------------------------------------------------------------------------------- 1 | export function encodeAscii(input: Uint8Array): string { 2 | return Buffer.from(input).toString('ascii'); 3 | } 4 | 5 | export function decodeAscii(input: string): Uint8Array { 6 | if (!isAscii(input)) throw new Error('Input is not ASCII'); 7 | return new Uint8Array(Buffer.from(input, 'ascii')); 8 | } 9 | 10 | export function isAscii(str: string) { 11 | // eslint-disable-next-line no-control-regex 12 | return /^[\x00-\x7F]+$/.test(str); 13 | } 14 | -------------------------------------------------------------------------------- /packages/internal/src/formatters/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { parseMimeText } from './parse'; 3 | 4 | describe('parse', () => { 5 | it('should handle legacy message content and subject', async () => { 6 | const legacyContent = readFileSync(__dirname + '/__tests__/legacy-content-and-subject.eml').toString(); 7 | 8 | const result = await parseMimeText(Buffer.from(legacyContent)); 9 | 10 | expect(result.mailData).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/internal/src/mailboxRuleEngine/rule.ts: -------------------------------------------------------------------------------- 1 | export type MailboxRuleCondition = { 2 | type: string; 3 | value: V; 4 | }; 5 | 6 | export type MailboxRuleAction = { 7 | type: string; 8 | value: V; 9 | }; 10 | 11 | export type MailboxRule = { 12 | id: string; 13 | name: string; 14 | isEnabled: () => Promise; 15 | condition: () => Promise>; 16 | actions: () => Promise[]>; 17 | stopFurtherProcessing?: boolean; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/keyring/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DERIVATION_PATH_MESSAGING_KEY_ROOT = 'Mailchain.Messaging.Root'; 2 | export const DERIVATION_PATH_IDENTITY_KEY_ROOT = 'Mailchain.Identity.Root'; 3 | export const DERIVATION_PATH_ENCRYPTION_KEY_ROOT = 'Mailchain.Encryption.Root'; 4 | export const DERIVATION_PATH_USER_PROFILE = 'UserProfile'; 5 | export const DERIVATION_PATH_USER_PROFILE_SETTINGS = 'UserProfile.Settings'; 6 | export const DERIVATION_PATH_INBOX_ROOT = 'Inbox'; 7 | export const DERIVATION_PATH_DATE_OFFSET = 'DateOffset'; 8 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/subject.ts: -------------------------------------------------------------------------------- 1 | export type CredentialSubject = CredentialSubjectOwnerOfMailchainMessagingKey; 2 | 3 | export type CredentialSubjectOwnerOfMailchainMessagingKey = { 4 | ownerOf: { 5 | type: 'MailchainMessagingKey'; 6 | address: string; 7 | }; 8 | }; 9 | 10 | export function createOwnerOfMessagingKeySubject(address: string): CredentialSubjectOwnerOfMailchainMessagingKey { 11 | return { 12 | ownerOf: { 13 | type: 'MailchainMessagingKey', 14 | address, 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/api/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import * as apiKeyToCryptoKey from './apiKeyToCryptoKey'; 2 | import * as cryptoKeyToApiKey from './cryptoKeyToApiKey'; 3 | 4 | const ApiKeyConvert = { 5 | private: apiKeyToCryptoKey.convertPrivate, 6 | public: apiKeyToCryptoKey.convertPublic, 7 | } as const; 8 | const CryptoKeyConvert = { 9 | public: cryptoKeyToApiKey.convertPublic, 10 | private: cryptoKeyToApiKey.convertPrivate, 11 | } as const; 12 | 13 | export { ApiKeyConvert, CryptoKeyConvert }; 14 | export * from './encoding'; 15 | export * from './address'; 16 | -------------------------------------------------------------------------------- /packages/internal/src/transport/serialization/chunk.ts: -------------------------------------------------------------------------------- 1 | export const CHUNK_LENGTH_1MB = 1024 * 1024; 2 | 3 | export function chunkBuffer(input: Uint8Array | Buffer, length: number): Buffer[] { 4 | let buffer = input instanceof Buffer ? input : Buffer.from(input); 5 | const result: Buffer[] = []; 6 | 7 | while (buffer.length > length) { 8 | const chunk = buffer.slice(0, length); 9 | buffer = buffer.slice(length); 10 | result.push(chunk); 11 | } 12 | 13 | if (buffer.length) { 14 | result.push(buffer); 15 | } 16 | 17 | return result; 18 | } 19 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/parseVerifiablePresentationRequest.ts: -------------------------------------------------------------------------------- 1 | import { VerifiablePresentationRequest } from './request'; 2 | 3 | export function parseVerifiablePresentationRequest(json: string): VerifiablePresentationRequest { 4 | const raw = JSON.parse(json); 5 | 6 | return { 7 | ...raw, 8 | signedCredentialExpiresAt: 9 | raw.signedCredentialExpiresAt != null ? new Date(raw.signedCredentialExpiresAt) : undefined, 10 | requestExpiresAfter: raw.requestExpiresAfter != null ? new Date(raw.requestExpiresAfter) : undefined, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/userMailboxHasher.ts: -------------------------------------------------------------------------------- 1 | import { publicKeyToBytes } from '@mailchain/crypto'; 2 | import { KeyRing } from '@mailchain/keyring'; 3 | import { sha3_256 } from '@noble/hashes/sha3'; 4 | import { UserMailbox } from '../user/types'; 5 | 6 | export type UserMailboxHasher = (userMailbox: UserMailbox) => Promise; 7 | 8 | export function createMailchainUserMailboxHasher(keyRing: KeyRing): UserMailboxHasher { 9 | return async (userMailbox) => 10 | sha3_256(await keyRing.accountIdentityKey().sign(publicKeyToBytes(userMailbox.identityKey))); 11 | } 12 | -------------------------------------------------------------------------------- /packages/addressing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addressComparison'; 2 | export { formatAddress } from './addressFormatting'; 3 | export * from './addressFromPublicKey'; 4 | export * from './addressPredicates'; 5 | export * from './checkAddressForErrors'; 6 | export * from './encoding'; 7 | export * from './errors'; 8 | export * from './test.constants'; 9 | export * from './parseWalletAddress'; 10 | export * from './protocols'; 11 | export * from './nameServiceAddress'; 12 | export type { NameServiceAddress as MailchainAddress } from './nameServiceAddress'; 13 | export * from './walletAddress'; 14 | -------------------------------------------------------------------------------- /packages/crypto/src/testing/private.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '../public'; 2 | import { PrivateKey } from '../private'; 3 | import { UnknownPublicKey } from './public'; 4 | 5 | export const PrivateKeyLen = 32; 6 | 7 | export class UnknownPrivateKey implements PrivateKey { 8 | bytes: Uint8Array; 9 | publicKey: PublicKey; 10 | curve = 'testcurve'; 11 | 12 | constructor(bytes?: Uint8Array) { 13 | this.bytes = bytes!; 14 | this.publicKey = new UnknownPublicKey(); 15 | } 16 | 17 | async sign(_message: Uint8Array): Promise { 18 | return new Uint8Array(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/encoding/src/base64.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeBase64, encodeBase64 } from './base64'; 2 | 3 | describe('test unicde characters encoded properly', () => { 4 | test('chinese', () => { 5 | expect(Buffer.from(decodeBase64(encodeBase64(Buffer.from('我你')))).toString()).toEqual('我你'); 6 | }); 7 | test('cyrillic', () => { 8 | expect(Buffer.from(decodeBase64(encodeBase64(Buffer.from('привет всем')))).toString()).toEqual('привет всем'); 9 | }); 10 | test('emoji', () => { 11 | expect(Buffer.from(decodeBase64(encodeBase64(Buffer.from('😀😀😀')))).toString()).toEqual('😀😀😀'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/encoding/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const EncodingTypes = { 2 | /** 3 | * Plain hex encoding value. 4 | */ 5 | Hex: 'hex/plain', 6 | /** 7 | * 0x prefixed hex encoding 8 | */ 9 | Hex0xPrefix: 'hex/0x-prefix', 10 | /** 11 | * With or without 0x prefix 12 | */ 13 | HexAny: 'hex/any', 14 | Base32: 'base32/plain', 15 | Base58: 'base58/plain', 16 | Base64: 'base64/plain', 17 | Base62Url: 'base64/url', 18 | Base58SubstrateAddress: 'base58/ss58-address', 19 | Utf8: 'text/utf-8', 20 | } as const; 21 | 22 | export type EncodingType = typeof EncodingTypes[keyof typeof EncodingTypes]; 23 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/cipher.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * NaCl with Elliptic Curve Diffie–Hellman key exchange 3 | */ 4 | export const NACLECDH = 0x2a; 5 | /** 6 | * NaCl with a secret key 7 | */ 8 | export const NACLSK = 0x2b; 9 | 10 | export const KindNaClSecretKey = 'nacl-secret-key'; 11 | 12 | export type EncryptedContent = Uint8Array; 13 | 14 | export type PlainContent = Uint8Array; 15 | 16 | export interface Decrypter { 17 | decrypt: (input: EncryptedContent) => Promise; 18 | } 19 | 20 | export interface Encrypter { 21 | encrypt: (input: PlainContent) => Promise; 22 | } 23 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/near/address.ts: -------------------------------------------------------------------------------- 1 | import { isHex } from '@mailchain/encoding'; 2 | 3 | export function validateNearAccountId(accountId: string) { 4 | return validateNearImplicitAccount(accountId) || validateNearNamedAccount(accountId); 5 | } 6 | 7 | export function validateNearImplicitAccount(address: string) { 8 | return address.length == 64 && isHex(address); 9 | } 10 | 11 | export function validateNearNamedAccount(address: string) { 12 | return ( 13 | address.length >= 2 && 14 | address.length <= 64 && 15 | /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(address) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/crypto/src/ed25519/hd.ts: -------------------------------------------------------------------------------- 1 | import { PrivateKey } from '../private'; 2 | import { ExtendedPrivateKey } from '../hd'; 3 | import { asED25519PrivateKey, ED25519PrivateKey } from './private'; 4 | 5 | export class ED25519ExtendedPrivateKey implements ExtendedPrivateKey { 6 | bytes: Uint8Array; 7 | privateKey: ED25519PrivateKey; 8 | 9 | private constructor(privateKey: ED25519PrivateKey) { 10 | this.privateKey = privateKey; 11 | this.bytes = privateKey.bytes; 12 | } 13 | static fromPrivateKey(privateKey: PrivateKey): ED25519ExtendedPrivateKey { 14 | return new this(asED25519PrivateKey(privateKey)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/signatures/src/mailchain_message_confirmation.ts: -------------------------------------------------------------------------------- 1 | import { encodeHex } from '@mailchain/encoding'; 2 | import { KeyRingDecrypter } from '@mailchain/keyring'; 3 | 4 | export const mailchainDeliveryConfirmationMessage = (deliveryRequestID: Uint8Array): Uint8Array => { 5 | return Buffer.from(`\x11Mailchain delivery confirmation:\n${encodeHex(deliveryRequestID)}`, 'utf-8'); 6 | }; 7 | 8 | export const signMailchainDeliveryConfirmation = (pk: KeyRingDecrypter, deliveryRequestID: Uint8Array) => { 9 | const msg: Uint8Array = mailchainDeliveryConfirmationMessage(deliveryRequestID); 10 | 11 | return pk.sign(msg); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/keyring/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Keyring 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/keyring 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/keyring 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The keyring package is used by `@mailchain/sdk` to support securely access messaging and account keys. 24 | -------------------------------------------------------------------------------- /packages/signatures/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Signatures 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/signatures 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/signatures 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The signatures package is used by `@mailchain/sdk` to support creating and verifying proofs. 24 | -------------------------------------------------------------------------------- /packages/encoding/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Encoding 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/encoding 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/encoding 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The encoding package is used by `@mailchain/sdk` to support encoding across different blockchain protocols. 24 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mailer/__snapshots__/content.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createContentBuffer example payload: original 1`] = `"{"authorMailAddress":"alice@mailchain.com","authorMessagingKey":"e2723caa23a5b511af5ad7b7ef6076e414ab7e75a9dc910ea60e417a2b770a5671","contentUri":"https://example.com","date":1591401600000,"mailerProof":{"params":{"authorContentSignature":"05060708","expires":1654473600000,"mailerMessagingKey":"e22e322f8740c60172111ac8eadcdda2512f90d06d0e503ef189979a159bece1e8"},"signature":"0a0b0c0d","version":"1.0"},"messageId":"message-id-0","to":["bob@mailchain.com"],"version":"1"}"`; 4 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mailer/types.ts: -------------------------------------------------------------------------------- 1 | import { MailAddress } from '../mail/types'; 2 | 3 | export type MailerData = { 4 | /** 5 | * Message subject line, this will become the `Subject:` header in the email. 6 | */ 7 | subject: string; 8 | /** 9 | * Reply-To address, this will become the `Reply-To:` header in the email. 10 | */ 11 | replyTo?: MailAddress; 12 | /** 13 | * HTML formatted message, this will become the `text/html` part of the email. 14 | */ 15 | html: string; 16 | /** 17 | * Plain text formatted message, this will become the `text/plain` part of the email. 18 | */ 19 | plainTextMessage: string; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/addressing/src/nameservices/matchesNameservice.ts: -------------------------------------------------------------------------------- 1 | import { NameServiceAddress } from '../nameServiceAddress'; 2 | import { NameserviceDescription } from './nameserviceDescriptions'; 3 | 4 | export function matchesNameservice( 5 | address: NameServiceAddress, 6 | nameserviceDescription: NameserviceDescription, 7 | ): string | undefined { 8 | const domainParts = address.domain.split('.'); 9 | if (domainParts[0] !== nameserviceDescription.name) return undefined; 10 | 11 | const usernameParts = address.username.split('.'); 12 | return nameserviceDescription.domains.find((domain) => usernameParts[usernameParts.length - 1] === domain); 13 | } 14 | -------------------------------------------------------------------------------- /packages/signatures/src/raw_ed25519.ts: -------------------------------------------------------------------------------- 1 | import { KindED25519, Signer, PublicKey, ErrorUnsupportedKey } from '@mailchain/crypto'; 2 | 3 | export async function signRawEd25519(signer: Signer, msg: Uint8Array) { 4 | switch (signer.curve) { 5 | case KindED25519: { 6 | return signer.sign(msg); 7 | } 8 | default: 9 | throw new ErrorUnsupportedKey(signer.curve); 10 | } 11 | } 12 | 13 | export async function verifyRawEd25519(key: PublicKey, msg: Uint8Array, signature: Uint8Array) { 14 | switch (key.curve) { 15 | case KindED25519: 16 | return key.verify(msg, signature); 17 | default: 18 | throw new ErrorUnsupportedKey(key.curve); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/crypto/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Crypto 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/crypto 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/crypto 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The crypto package is used by `@mailchain/sdk` to support securely registering blockchain addresses and sending messages via Mailchain. 24 | -------------------------------------------------------------------------------- /packages/api/src/helpers/encoding.ts: -------------------------------------------------------------------------------- 1 | import { EncodingType, EncodingTypes } from '@mailchain/encoding'; 2 | import { AddressEncodingEnum } from '../api/api'; 3 | 4 | export function encodingTypeToEncodingEnum(encoding: EncodingType): AddressEncodingEnum { 5 | switch (encoding) { 6 | case EncodingTypes.Hex: 7 | return AddressEncodingEnum.HexPlain; 8 | case EncodingTypes.Hex0xPrefix: 9 | return AddressEncodingEnum.Hex0xPrefix; 10 | case EncodingTypes.Utf8: 11 | return AddressEncodingEnum.TextUtf8; 12 | case EncodingTypes.Base58: 13 | return AddressEncodingEnum.Base58Plain; 14 | } 15 | throw new Error(`unsupported encoding by API of [${encoding}]`); 16 | } 17 | -------------------------------------------------------------------------------- /packages/addressing/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Addressing 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/addressing 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/addressing 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The addressing package is used by `@mailchain/sdk` to support securely sending and resolving blockchain addresses for the Mailchain protocol. 24 | -------------------------------------------------------------------------------- /packages/crypto/src/secp256r1/public.ts: -------------------------------------------------------------------------------- 1 | import { secp256r1 } from '@noble/curves/p256'; 2 | import { PublicKey } from '../public'; 3 | import { KindSECP256R1 } from '../keys'; 4 | 5 | export const SECP256R1PublicKeyLength = 33; 6 | 7 | export class SECP256R1PublicKey implements PublicKey { 8 | readonly curve: string = KindSECP256R1; 9 | 10 | constructor(public readonly bytes: Uint8Array) { 11 | if (this.bytes.length !== SECP256R1PublicKeyLength) { 12 | throw RangeError('bytes are not a valid secp256r1 public key'); 13 | } 14 | } 15 | 16 | async verify(message: Uint8Array, sig: Uint8Array): Promise { 17 | return secp256r1.verify(sig, message, this.bytes); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/solana/address.ts: -------------------------------------------------------------------------------- 1 | import { KindED25519, PublicKey } from '@mailchain/crypto'; 2 | import { decodeBase58 } from '@mailchain/encoding'; 3 | 4 | export function validateSolanaAddress(addressID: string) { 5 | try { 6 | const address = decodeBase58(addressID); 7 | return address.length === 32; 8 | } catch (e) { 9 | return false; 10 | } 11 | } 12 | 13 | export function solanaAddressFromPublicKey(publicKey: PublicKey): Uint8Array { 14 | if (publicKey.curve !== KindED25519) throw new Error('Only ED25519 is supported for Solana'); 15 | if (publicKey.bytes.length !== 32) throw new Error('Invalid public key length for Solana'); 16 | return publicKey.bytes; 17 | } 18 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/termsOfUse.ts: -------------------------------------------------------------------------------- 1 | import { DecentralizedIdentifier } from './did'; 2 | 3 | export type TermsOfUse = { 4 | type: 'IssuerPolicy' | 'HolderPolicy'; 5 | id?: string; 6 | effect: 'Allow' | 'Deny'; 7 | /** 8 | * assigner formatted as a {@link DecentralizedIdentifier}, is who is defining the terms of use. Often assigner is the issuer of the credential, however it may be another identity. 9 | */ 10 | assigner: DecentralizedIdentifier; 11 | /** 12 | * assigner formatted as a {@link DecentralizedIdentifier}, is who the terms of use is intended for. 13 | */ 14 | assignee: DecentralizedIdentifier; 15 | actions: string[]; 16 | resources: string[]; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/message-composer/src/consts.ts: -------------------------------------------------------------------------------- 1 | /** Recommended line length limit defined in https://www.rfc-editor.org/rfc/rfc5322#section-2.1.1 */ 2 | export const LINE_LENGTH_FOLD = 78; 3 | 4 | export const CRLF = '\r\n' as const; 5 | 6 | export const HTAB = ' ' as const; 7 | 8 | export const HEADER_LABELS = { 9 | MimeVersion: 'MIME-Version', 10 | MessageId: 'Message-ID', 11 | Subject: 'Subject', 12 | Date: 'Date', 13 | From: 'From', 14 | To: 'To', 15 | Cc: 'Cc', 16 | Bcc: 'Bcc', 17 | ReplyTo: 'Reply-To', 18 | ContentType: 'Content-Type', 19 | ContentTransferEncoding: 'Content-Transfer-Encoding', 20 | ContentDisposition: 'Content-Disposition', 21 | ContentId: 'Content-ID', 22 | } as const; 23 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/privateMessagingKey.ts: -------------------------------------------------------------------------------- 1 | import { PrivateKey } from '@mailchain/crypto'; 2 | import { MailchainResult } from '@mailchain/internal'; 3 | import { defaultConfiguration } from '@mailchain/internal/configuration'; 4 | import { GetExportablePrivateMessagingKeyError, PrivateMessagingKeys } from '@mailchain/internal/messagingKeys'; 5 | import { KeyRing } from '@mailchain/keyring'; 6 | 7 | export async function getPrivateMessagingKey( 8 | address: string, 9 | keyRing: KeyRing, 10 | ): Promise> { 11 | return PrivateMessagingKeys.create(defaultConfiguration).getExportablePrivateMessagingKey(address, keyRing); 12 | } 13 | -------------------------------------------------------------------------------- /packages/message-composer/src/hasOnlyAscii.ts: -------------------------------------------------------------------------------- 1 | const SP_CHAR_CODE = 32; 2 | const HTAB_CHAR_CODE = 9; 3 | 4 | export function hasOnlyPrintableUsAscii(str: string, allowWSC = true): boolean { 5 | // A field body may be composed of printable US-ASCII characters as well as the space (SP, ASCII value 32) and horizontal tab (HTAB, ASCII value 9) 6 | // https://www.rfc-editor.org/rfc/rfc5322#section-2.2 7 | for (let i = 0; i < str.length; i++) { 8 | const charCode = str.charCodeAt(i); 9 | if ( 10 | (charCode >= 33 && charCode <= 126) || 11 | (allowWSC && (charCode === SP_CHAR_CODE || charCode === HTAB_CHAR_CODE)) 12 | ) { 13 | continue; 14 | } 15 | return false; 16 | } 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/filecoin/address.ts: -------------------------------------------------------------------------------- 1 | import { isBase32 } from '@mailchain/encoding'; 2 | import { FILECOIN_PREFIX_F4_ETHEREUM, FILECOIN_PREFIX_T4_ETHEREUM } from './const'; 3 | import { convertFilDelegatedAddressToEthAddress } from './delegatedAddress'; 4 | 5 | export function validateFilecoinAddress(address: string): boolean { 6 | if (address.length < 42 && address.length > 49) { 7 | return false; 8 | } else if (!address.startsWith(FILECOIN_PREFIX_F4_ETHEREUM) && !address.startsWith(FILECOIN_PREFIX_T4_ETHEREUM)) { 9 | return false; 10 | } else if (!isBase32(address.slice(4))) { 11 | return false; 12 | } 13 | 14 | return convertFilDelegatedAddressToEthAddress(address).error === undefined; 15 | } 16 | -------------------------------------------------------------------------------- /packages/signatures/src/keyreg/__snapshots__/message.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`CreateProofMessage simple-v1-en_US: simple-v1-en_US 1`] = ` 4 | "Welcome to Mailchain! 5 | 6 | Please sign to start using this address with Mailchain. This will not trigger a blockchain transaction or cost any gas fees. 7 | 8 | What's happening? 9 | A messaging key will be registered with this address and used only for messaging. It will replace any existing registered messaging keys. 10 | 11 | Technical Details: 12 | Address: 0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761 13 | Messaging key: 0xe2723caa23a5b511af5ad7b7ef6076e414ab7e75a9dc910ea60e417a2b770a5671 14 | Nonce: 1" 15 | `; 16 | -------------------------------------------------------------------------------- /packages/signatures/src/keyreg/message.ts: -------------------------------------------------------------------------------- 1 | import { publicKeyToBytes, PublicKey } from '@mailchain/crypto'; 2 | import { encode } from '@mailchain/encoding'; 3 | import { ProofParams } from './params'; 4 | import { getTemplate } from './templates'; 5 | 6 | export function createProofMessage(params: ProofParams, address: Uint8Array, msgKey: PublicKey, nonce: number): string { 7 | const encodedAddress = encode(params.AddressEncoding, address); 8 | const descriptivePublicKey = publicKeyToBytes(msgKey); 9 | const encodedPublicKey = encode(params.PublicKeyEncoding, descriptivePublicKey); 10 | const templateMessageFunc = getTemplate(params); 11 | 12 | return templateMessageFunc(encodedAddress, encodedPublicKey, nonce); 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | jobs: 4 | sync-repo: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | with: 9 | fetch-depth: 0 10 | 11 | - uses: mailchain/copybara-action@1.0 12 | with: 13 | ssh_key: ${{ secrets.SSH_KEY }} 14 | access_token: ${{ secrets.ACCESS_TOKEN }} 15 | sot_repo: mailchain/monorepo 16 | destination_repo: mailchain/mailchain-sdk-js 17 | pr_include: '**' 18 | push_include: 'packages/sdk-js/**' 19 | pr_move: | 20 | ||packages/sdk-js 21 | -------------------------------------------------------------------------------- /packages/addressing/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class BadlyFormattedAddressError extends Error { 2 | readonly type = 'badly_formatted_address'; 3 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#badly'; 4 | constructor() { 5 | super('Address format is invalid. Check that the format follows the Mailchain address standard.'); 6 | } 7 | } 8 | 9 | export class IdentityProviderAddressInvalidError extends Error { 10 | readonly type = 'identity_provider_address_invalid'; 11 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#identity_provider_address_invalid'; 12 | constructor() { 13 | super(`Address is not valid for the identity provider. Check address is valid for the identity provider.`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/addressing/src/parseWalletAddress.ts: -------------------------------------------------------------------------------- 1 | import { NameServiceAddress } from './nameServiceAddress'; 2 | import { ALL_PROTOCOLS, MAILCHAIN, ProtocolType } from './protocols'; 3 | 4 | export function parseWalletAddress( 5 | address: NameServiceAddress, 6 | ): { protocol: ProtocolType; network?: string } | undefined { 7 | const domainParts = address.domain.split('.'); 8 | if (domainParts.length <= 2) return undefined; 9 | 10 | const protocol = ALL_PROTOCOLS.find((search) => domainParts[0] == search); 11 | if (protocol == null || protocol === MAILCHAIN) return undefined; 12 | 13 | const protocolIndex = domainParts.indexOf(protocol); 14 | const network = domainParts[protocolIndex - 1]; 15 | 16 | return { protocol, network }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/addressing/src/addressCasing.ts: -------------------------------------------------------------------------------- 1 | import { ALGORAND, ETHEREUM, MAILCHAIN, NEAR, ProtocolType, SOLANA, SUBSTRATE, TEZOS } from './protocols'; 2 | 3 | export function casingByProtocol(value: string, protocol: ProtocolType): string { 4 | switch (protocol) { 5 | case MAILCHAIN: 6 | return value.toLowerCase(); // case insensitive 7 | case ETHEREUM: 8 | return value.toLowerCase(); // case insensitive 9 | case SOLANA: 10 | case SUBSTRATE: 11 | case TEZOS: 12 | return value; // substrate&tezos encoding is case sensitive 13 | case ALGORAND: 14 | return value.toLowerCase(); // case insensitive 15 | case NEAR: 16 | return value.toLowerCase(); 17 | default: 18 | throw new Error(`casing for protocol [${protocol}] not defined`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/internal/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Internal Library 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/internal 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/internal 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The internal package is used by `@mailchain/sdk` to support it's operations. It's available to developers to use however the interface is not yet stable so expect changes. Stable interfaces will be exposed to their own packages. 24 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/addressNonce.ts: -------------------------------------------------------------------------------- 1 | import { ProtocolType } from '@mailchain/addressing'; 2 | import { defaultConfiguration } from '@mailchain/internal/configuration'; 3 | import { GetMessagingKeyLatestNonceResult, AddressNonce } from '@mailchain/internal/messagingKeys'; 4 | /** 5 | * Get the latest nonce for an address. 6 | * 7 | * @param address the protocol get the latest nonce for. 8 | * @param protocol where to find the address. 9 | * @returns The latest nonce for the given address. 10 | */ 11 | export async function getMessagingKeyLatestNonce( 12 | address: string, 13 | protocol: ProtocolType, 14 | ): Promise { 15 | return AddressNonce.create(defaultConfiguration).getMessagingKeyLatestNonce(address, protocol); 16 | } 17 | -------------------------------------------------------------------------------- /packages/message-composer/src/headerFactories.ts: -------------------------------------------------------------------------------- 1 | import { HEADER_LABELS } from './consts'; 2 | import { Header, HeaderAttribute, MessageIdsHeader } from './types'; 3 | 4 | export function createHeader(label: string, value: T, ...attrs: HeaderAttribute[]): Header { 5 | return { label, value, attrs }; 6 | } 7 | 8 | export function createMessageIdHeader(label: string, ids: string[], ...attrs: HeaderAttribute[]): MessageIdsHeader { 9 | return { 10 | label, 11 | value: { 12 | type: 'message-ids', 13 | ids, 14 | }, 15 | attrs, 16 | }; 17 | } 18 | 19 | export function contentTypeBoundaryHeader(type: 'alternative' | 'mixed', boundary: string): Header { 20 | return createHeader(HEADER_LABELS.ContentType, `multipart/${type}`, ['boundary', boundary]); 21 | } 22 | -------------------------------------------------------------------------------- /packages/internal/src/transport/payload/verifier.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from './payload'; 2 | 3 | export class ErrorPayloadSignatureInvalid extends Error { 4 | constructor() { 5 | super('payload signature invalid'); 6 | } 7 | } 8 | 9 | export class PayloadOriginVerifier { 10 | static create() { 11 | return new PayloadOriginVerifier(); 12 | } 13 | 14 | async verifyPayloadOrigin(payload: Payload) { 15 | if (!payload.Headers) { 16 | throw new Error('payload does not contain Headers'); 17 | } 18 | 19 | if (!payload.Headers.Origin) { 20 | throw new Error('payload does not contain Headers.Origin'); 21 | } 22 | 23 | if (!(await payload.Headers.Origin.verify(payload.Content, payload.Headers.ContentSignature))) { 24 | throw new ErrorPayloadSignatureInvalid(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/internal/src/sending/verifiablePresentationRequest/payload.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from '../../transport/payload'; 2 | import { PayloadHeaders } from '../../transport/payload/headers'; 3 | 4 | export type VerifiablePresentationHeaders = PayloadHeaders<'application/vnd.mailchain.verified-credential-request'>; 5 | 6 | export function isVerifiablePresentationHeaders(headers: PayloadHeaders): headers is VerifiablePresentationHeaders { 7 | return headers.ContentType === 'application/vnd.mailchain.verified-credential-request'; 8 | } 9 | 10 | export type VerifiablePresentationPayload = Payload; 11 | 12 | export function isVerifiablePresentationPayload(payload: Payload): payload is VerifiablePresentationPayload { 13 | return isVerifiablePresentationHeaders(payload.Headers); 14 | } 15 | -------------------------------------------------------------------------------- /packages/encoding/src/ascii.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeAscii, encodeAscii } from './ascii'; 2 | 3 | describe('encode ascii', () => { 4 | it('should decode ascii', () => { 5 | const input = 'Hello World!'; 6 | const actual = decodeAscii(input); 7 | 8 | expect(actual).toEqual( 9 | Uint8Array.from([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21]), 10 | ); 11 | }); 12 | it('should encode ascii', () => { 13 | const input = Uint8Array.from([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21]); 14 | const actual = encodeAscii(input); 15 | expect(actual).toEqual('Hello World!'); 16 | }); 17 | 18 | it('should throw error when decoding non ascii', () => { 19 | const input = 'Здраво Свету!'; 20 | expect(() => decodeAscii(input)).toThrowError('Input is not ASCII'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/presentation.test.ts: -------------------------------------------------------------------------------- 1 | import { W3C_CREDENTIALS_CONTEXT } from './context'; 2 | import { createPresentationPayload } from './presentation'; 3 | 4 | describe('createPresentationPayload', () => { 5 | it('create', () => { 6 | const actual = createPresentationPayload({ 7 | requestId: 'request-id', 8 | holder: 'did:mailchain:holder', 9 | issuanceDate: new Date(2020, 1, 1), 10 | verifiableCredential: { 11 | '@context': [W3C_CREDENTIALS_CONTEXT], 12 | credentialSubject: { 13 | id: 'credentialSubjectId', 14 | }, 15 | issuanceDate: new Date(2000, 1, 1).toISOString(), 16 | issuer: { id: 'did:mailchain:issuer' }, 17 | proof: { id: 'proof' }, 18 | type: ['type'], 19 | }, 20 | verifier: 'did:mailchain:verifier', 21 | }); 22 | 23 | expect(actual).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/internal/src/transport/mailer/mailerProof.test.ts: -------------------------------------------------------------------------------- 1 | import { BobED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { MailerProof } from '@mailchain/signatures'; 3 | import { createMailerProofBuffer, parseMailerProofFromJSON } from './mailerProof'; 4 | 5 | describe('round trip', () => { 6 | it('example payload', () => { 7 | const original: MailerProof = { 8 | params: { 9 | expires: new Date('2022-06-06'), 10 | mailerMessagingKey: BobED25519PublicKey, 11 | authorContentSignature: new Uint8Array([0x05, 0x06, 0x07, 0x08]), 12 | }, 13 | signature: new Uint8Array([0x0a, 0xb, 0xc, 0xd]), 14 | version: '1.0', 15 | }; 16 | 17 | const bufferedContent = createMailerProofBuffer(original); 18 | 19 | const actual = parseMailerProofFromJSON(bufferedContent); 20 | 21 | expect(actual).toEqual(original); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/encoding/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/encoding", 3 | "version": "0.31.0", 4 | "description": "Mailchain encoding tools", 5 | "license": "Apache-2.0", 6 | "type": "commonjs", 7 | "author": { 8 | "name": "Mailchain", 9 | "email": "support@mailchain.co", 10 | "url": "https://mailchain.com/" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 15 | "directory": "encoding" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 19 | }, 20 | "dependencies": { 21 | "@noble/hashes": "^1.3.0", 22 | "@scure/base": "^1.1.1" 23 | }, 24 | "scripts": { 25 | "build": "run -T tsup", 26 | "test": "run -T jest", 27 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 28 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/internal/src/transport/deliveryRequests/delivery.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | 3 | import { ExtendedPrivateKey, PublicKey, RandomFunction, secureRandom } from '@mailchain/crypto'; 4 | import { protocol } from '../../protobuf/protocol/protocol'; 5 | 6 | import { createEnvelope } from './envelope'; 7 | 8 | export async function createDelivery( 9 | recipientMessagingKey: PublicKey, 10 | messageRootEncryptionKey: ExtendedPrivateKey, 11 | messageURI: string, 12 | rand: RandomFunction = secureRandom, 13 | ): Promise { 14 | const payload = { 15 | envelope: await createEnvelope(recipientMessagingKey, messageRootEncryptionKey, messageURI, rand), 16 | } as protocol.IDelivery; 17 | 18 | const errMsg = protocol.Delivery.verify(payload); 19 | if (errMsg) throw Error(errMsg); 20 | 21 | return protocol.Delivery.create(payload); 22 | } 23 | -------------------------------------------------------------------------------- /packages/keyring/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/keyring", 3 | "version": "0.31.0", 4 | "description": "Mailchain keyring", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "keyring" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@mailchain/addressing": "0.31.0", 21 | "@mailchain/crypto": "0.31.0", 22 | "@mailchain/encoding": "0.31.0" 23 | }, 24 | "scripts": { 25 | "build": "run -T tsup", 26 | "test": "run -T jest", 27 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 28 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/crypto/src/ed25519/public.ts: -------------------------------------------------------------------------------- 1 | import nacl from 'tweetnacl'; 2 | import { KindED25519 } from '../keys'; 3 | import { PublicKey } from '../public'; 4 | 5 | export const ED25519PublicKeyLen = 32; 6 | 7 | export class ED25519PublicKey implements PublicKey { 8 | readonly curve: string = KindED25519; 9 | readonly bytes: Uint8Array; 10 | 11 | constructor(bytes: Uint8Array) { 12 | if (bytes.length !== ED25519PublicKeyLen) { 13 | throw new RangeError('invalid public key length'); 14 | } 15 | this.bytes = bytes; 16 | } 17 | 18 | async verify(message: Uint8Array, sig: Uint8Array): Promise { 19 | return nacl.sign.detached.verify(message, sig, this.bytes); 20 | } 21 | } 22 | 23 | export function asED25519PublicKey(key: PublicKey): ED25519PublicKey { 24 | if (key.constructor !== ED25519PublicKey) { 25 | throw new Error('key must be ed25519'); 26 | } 27 | 28 | return key; 29 | } 30 | -------------------------------------------------------------------------------- /packages/addressing/src/walletAddress.ts: -------------------------------------------------------------------------------- 1 | import { casingByProtocol } from './addressCasing'; 2 | import { createNameServiceAddress, NameServiceAddress as MailchainAddress } from './nameServiceAddress'; 3 | import { MAILCHAIN, ProtocolType } from './protocols'; 4 | 5 | /** 6 | * Helper method for creating of {@link MailchainAddress} for when the address is with specific protocol. 7 | * It provides the correct casing for the username. 8 | */ 9 | export function createWalletAddress( 10 | username: string, 11 | protocol: ProtocolType, 12 | mailchainAddressDomain: string, 13 | ): MailchainAddress { 14 | protocol = protocol.toLowerCase() as ProtocolType; 15 | const casedUsername = casingByProtocol(username, protocol); 16 | if (protocol === MAILCHAIN) return createNameServiceAddress(casedUsername, mailchainAddressDomain); 17 | return createNameServiceAddress(casedUsername, protocol, mailchainAddressDomain); 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain API 2 | 3 | Mailchain is a messaging protocol that lets users communicate across protocols. Using Mailchain you can send messages to any blockchain address on different protocols. 4 | 5 | For full usage examples view the [developer docs](https://docs.mailchain.com). 6 | 7 | ## Installing 8 | 9 | Using npm: 10 | 11 | ```bash 12 | $ npm install @mailchain/api 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ```bash 18 | $ yarn add @mailchain/api 19 | ``` 20 | 21 | ## Purpose 22 | 23 | The signatures package is used by `@mailchain/sdk` to communicate to Mailchain's protocol and services. 24 | 25 | **Note: The API package is not intended to be used directly. Instead it should be used via the SDK as the majority of Mailchain's API's are authenticated. Requests need to be encrypted and signed before sending to across the wire. Response need to be decrypted and or verified before being used.** 26 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { MessagingKeys, ResolveIndividualAddressResult } from '@mailchain/internal/messagingKeys'; 2 | import { Configuration, defaultConfiguration } from '@mailchain/internal/configuration'; 3 | 4 | /** 5 | * Resolve the address and returns proven messaging key for an address. 6 | * @param address Address to resolve. 7 | * 8 | * @returns A {@link ResolvedAddress resolved address}. 9 | * 10 | * @example 11 | * import { resolveAddress } from '@mailchain/sdk'; 12 | * 13 | * const {data: resolvedAddress, error} = await resolveAddress(address); 14 | * if (error != null) // handle error 15 | * else console.log(resolvedAddress); 16 | */ 17 | export async function resolveAddress( 18 | address: string, 19 | config: Configuration = defaultConfiguration, 20 | ): Promise { 21 | return MessagingKeys.create(config).resolveIndividual(address); 22 | } 23 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/ecdh/ecdh.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PublicKey } from '../../ed25519/test.const'; 2 | import { AliceUnknownPublicKey } from '../../testing/public'; 3 | import { ED25519KeyExchange } from './ed25519'; 4 | import { fromPublicKey } from './'; 5 | 6 | describe('FromPublicKey', () => { 7 | const tests = [ 8 | { 9 | name: 'creates-ed25519-key-exchange', 10 | key: AliceED25519PublicKey, 11 | expected: ED25519KeyExchange, 12 | shouldThrow: false, 13 | }, 14 | { 15 | name: 'fails-to-creates-with-unknown-key', 16 | key: AliceUnknownPublicKey, 17 | expected: null, 18 | shouldThrow: true, 19 | }, 20 | ]; 21 | test.each(tests)('$name', async (test) => { 22 | if (test.shouldThrow) { 23 | expect(() => { 24 | fromPublicKey(test.key); 25 | }).toThrow(); 26 | } else { 27 | expect(fromPublicKey(test.key).constructor).toEqual(test.expected); 28 | } 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/signatures/src/keyreg/params.ts: -------------------------------------------------------------------------------- 1 | import { EncodingType, EncodingTypes } from '@mailchain/encoding'; 2 | import { ProtocolType, encodingByProtocol } from '@mailchain/addressing'; 3 | 4 | export interface ProofParams { 5 | AddressEncoding: EncodingType; 6 | PublicKeyEncoding: EncodingType; 7 | Locale: string; 8 | Variant: string; 9 | } 10 | 11 | export function getLatestProofParams(protocol: ProtocolType, locale: string): ProofParams { 12 | const addressEncoding = encodingByProtocol(protocol); 13 | return { 14 | AddressEncoding: addressEncoding, 15 | PublicKeyEncoding: EncodingTypes.Hex0xPrefix, 16 | Locale: locale, 17 | Variant: 'simple-v1', 18 | }; 19 | } 20 | 21 | export function getMailchainUsernameParams(): ProofParams { 22 | return { 23 | AddressEncoding: EncodingTypes.Utf8, 24 | PublicKeyEncoding: EncodingTypes.Hex0xPrefix, 25 | Locale: 'en', 26 | Variant: 'mailchain-username', 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/crypto/src/ed25519/derivation.ts: -------------------------------------------------------------------------------- 1 | import { blake2b } from '@noble/hashes/blake2b'; 2 | import { chainCodeFromDeriveIndex, ExtendedPrivateKey } from '../hd'; 3 | import { asED25519PrivateKey, ED25519PrivateKey } from './private'; 4 | import { ED25519ExtendedPrivateKey } from './hd'; 5 | 6 | // 'Ed25519HDKD' encoded with length prefix 7 | const HDKD = new Uint8Array([44, 69, 100, 50, 53, 53, 49, 57, 72, 68, 75, 68]); 8 | 9 | export function ed25519DeriveHardenedKey( 10 | parentKey: ExtendedPrivateKey, 11 | index: string | number | Uint8Array, 12 | ): ED25519ExtendedPrivateKey { 13 | const chainCode = chainCodeFromDeriveIndex(index); 14 | const seed = asED25519PrivateKey(parentKey.privateKey).bytes.slice(0, 32); 15 | 16 | return ED25519ExtendedPrivateKey.fromPrivateKey( 17 | ED25519PrivateKey.fromSeed( 18 | blake2b(Uint8Array.from([...HDKD, ...seed, ...chainCode]), { 19 | dkLen: 256 / 8, 20 | }), 21 | ), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/message-composer/src/messageComposerContext.ts: -------------------------------------------------------------------------------- 1 | import { encodeBase64, encodeBase64UrlSafe, decodeUtf8 } from '@mailchain/encoding'; 2 | 3 | export type MessageComposerContext = { 4 | random: (len: number) => Promise; 5 | decodeUtf8: (content: string) => Promise; 6 | encodeBase64: (content: Uint8Array, urlSafe?: boolean) => Promise; 7 | }; 8 | 9 | export function defaultMessageComposerContext(): MessageComposerContext { 10 | return { 11 | random: (len) => 12 | globalThis.crypto != null 13 | ? Promise.resolve(globalThis.crypto.getRandomValues(new Uint8Array(len))) 14 | : import('crypto').then(({ webcrypto }) => webcrypto.getRandomValues(new Uint8Array(len))), 15 | decodeUtf8: (content) => Promise.resolve(decodeUtf8(content)), 16 | encodeBase64: (content, urlSafe) => 17 | urlSafe ? Promise.resolve(encodeBase64UrlSafe(content)) : Promise.resolve(encodeBase64(content)), 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/payload.test.ts: -------------------------------------------------------------------------------- 1 | import { createVerifiableCredential } from './payload'; 2 | 3 | describe('createVerifiableCredential', () => { 4 | it('create - ownerOf', () => { 5 | const actual = createVerifiableCredential({ 6 | credentialSubjects: [ 7 | { 8 | ownerOf: { 9 | address: 'address', 10 | type: 'MailchainMessagingKey', 11 | }, 12 | }, 13 | ], 14 | issuanceDate: new Date(2000, 1, 1), 15 | issuerId: 'did:mailchain:issuer', 16 | proof: { 17 | type: 'proof-type', 18 | }, 19 | termsOfUse: [ 20 | { 21 | id: 'terms-1', 22 | actions: ['action'], 23 | assignee: 'did:mailchain:assignee', 24 | assigner: 'did:mailchain:assigner', 25 | effect: 'Allow', 26 | resources: ['*'], 27 | type: 'HolderPolicy', 28 | }, 29 | ], 30 | type: 'MailchainMessagingKeyCredential', 31 | }); 32 | 33 | expect(actual).toMatchSnapshot(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/addressing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/addressing", 3 | "version": "0.31.0", 4 | "description": "Mailchain addressing", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "addressing" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@ethersproject/transactions": "^5.7.0", 21 | "@mailchain/crypto": "0.31.0", 22 | "@mailchain/encoding": "0.31.0", 23 | "@noble/hashes": "^1.3.0", 24 | "lodash": "^4.17.21" 25 | }, 26 | "scripts": { 27 | "build": "run -T tsup", 28 | "test": "run -T jest", 29 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 30 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/addressing/src/addressFromPublicKey.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceSECP256K1PublicKey, BobSECP256K1PublicKey } from '@mailchain/crypto/secp256k1/test.const'; 2 | import { addressFromPublicKey } from './addressFromPublicKey'; 3 | import { ETHEREUM } from './protocols'; 4 | import { AliceSECP256K1PublicAddress, BobSECP256K1PublicAddress } from './protocols/ethereum/test.const'; 5 | 6 | const testCases = [ 7 | { 8 | caseName: 'ethereum address', 9 | publicKey: AliceSECP256K1PublicKey, 10 | protocol: ETHEREUM, 11 | expectedAddress: AliceSECP256K1PublicAddress, 12 | }, 13 | { 14 | caseName: 'ethereum address', 15 | publicKey: BobSECP256K1PublicKey, 16 | protocol: ETHEREUM, 17 | expectedAddress: BobSECP256K1PublicAddress, 18 | }, 19 | ]; 20 | 21 | test.each(testCases)('$caseName', async ({ publicKey, protocol, expectedAddress }) => { 22 | const result = await addressFromPublicKey(publicKey, protocol); 23 | 24 | expect(result).toEqual(expectedAddress); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/internal/src/transport/payload/payload.ts: -------------------------------------------------------------------------------- 1 | import { ED25519ExtendedPrivateKey } from '@mailchain/crypto'; 2 | import { CHUNK_LENGTH_1MB, encryptPayload, serialize } from '../serialization'; 3 | import { SerializablePayloadHeadersImpl } from './headersSerialize'; 4 | import { PayloadHeaders } from './headers'; 5 | 6 | /** 7 | * Payload. 8 | */ 9 | export interface Payload { 10 | /** 11 | * @see defaults to {@link PayloadHeaders} 12 | */ 13 | Headers: H; 14 | /** 15 | * Raw/data/object that are decrypted and parsed base on the the headers. 16 | */ 17 | Content: Buffer; 18 | } 19 | 20 | export async function serializeAndEncryptPayload( 21 | payload: Payload, 22 | payloadRootEncryptionKey: ED25519ExtendedPrivateKey, 23 | ) { 24 | const headers = new SerializablePayloadHeadersImpl().serialize(payload.Headers); 25 | return serialize(await encryptPayload(headers, payload.Content, payloadRootEncryptionKey, CHUNK_LENGTH_1MB)); 26 | } 27 | -------------------------------------------------------------------------------- /packages/encoding/src/base64.ts: -------------------------------------------------------------------------------- 1 | export function decodeBase64(input: string): Uint8Array { 2 | const output = Buffer.from(input, 'base64'); 3 | if (input.length > 0 && output.length === 0) { 4 | throw new Error('could not decode input'); 5 | } 6 | 7 | return Uint8Array.from(output); 8 | } 9 | 10 | export function encodeBase64(input: Uint8Array): string { 11 | return Buffer.from(input).toString('base64'); 12 | } 13 | 14 | export function decodeBase64UrlSafe(input: string): Uint8Array { 15 | let encoded = input.replace('-', '+').replace('_', '/'); 16 | while (encoded.length % 4) encoded += '='; 17 | const output = Buffer.from(encoded, 'base64'); 18 | if (input.length > 0 && output.length === 0) { 19 | throw new Error('could not decode input'); 20 | } 21 | 22 | return Uint8Array.from(output); 23 | } 24 | 25 | export function encodeBase64UrlSafe(input: Uint8Array): string { 26 | return Buffer.from(input).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); 27 | } 28 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/ecdh/ecdh.ts: -------------------------------------------------------------------------------- 1 | import { KeyExchange } from '..'; 2 | import { RandomFunction, secureRandom } from '../../rand'; 3 | import { PublicKey } from '../../public'; 4 | import { PrivateKey } from '../../private'; 5 | import { ED25519PublicKey } from '../../ed25519/public'; 6 | import { ED25519PrivateKey } from '../../ed25519/private'; 7 | import { ED25519KeyExchange } from './ed25519'; 8 | 9 | export function fromPublicKey(publicKey: PublicKey, rand: RandomFunction = secureRandom): KeyExchange { 10 | switch (publicKey.constructor) { 11 | case ED25519PublicKey: 12 | return new ED25519KeyExchange(rand); 13 | default: 14 | throw RangeError('unknown public key type'); 15 | } 16 | } 17 | 18 | export function fromPrivateKey(privateKey: PrivateKey, rand: RandomFunction = secureRandom): KeyExchange { 19 | switch (privateKey.constructor) { 20 | case ED25519PrivateKey: 21 | return new ED25519KeyExchange(rand); 22 | default: 23 | throw RangeError('unknown private key type'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/api/src/axios/axios.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithPublicKey } from '@mailchain/crypto'; 2 | import axios, { AxiosInstance } from 'axios'; 3 | import { encodeBase64UrlSafe } from '@mailchain/encoding'; 4 | import { signJWT } from '../jwt'; 5 | import { createTokenPayload } from './token'; 6 | 7 | export const getAxiosWithSigner = (requestKey: SignerWithPublicKey): AxiosInstance => { 8 | const axiosInstance = axios.create(); 9 | axiosInstance.interceptors.request.use(async (request) => { 10 | if (request.headers) { 11 | const expires = Math.floor(Date.now() / 1000 + 60 * 5); // 5 mins 12 | const tokenPayload = createTokenPayload( 13 | new URL(request?.url ?? ''), 14 | request.method?.toUpperCase() ?? '', 15 | request.data, 16 | expires, 17 | ); 18 | const token = await signJWT(requestKey, tokenPayload); 19 | request.headers.Authorization = `vapid t=${token}, k=${encodeBase64UrlSafe(requestKey.publicKey.bytes)}`; 20 | } 21 | return request; 22 | }); 23 | 24 | return axiosInstance; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/message-composer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/message-composer", 3 | "version": "0.31.0", 4 | "description": "Mailchain MIME message composer. RFC-5322 compliant", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "message-composer" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@mailchain/encoding": "0.31.0", 21 | "date-fns": "^2.29.2" 22 | }, 23 | "scripts": { 24 | "build": "run -T tsup", 25 | "test": "run -T jest", 26 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 27 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 28 | }, 29 | "engines": { 30 | "node": ">=15.x" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "18.11.18" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/signatures/src/verify.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@mailchain/crypto'; 2 | import { decodeUtf8 } from '@mailchain/encoding'; 3 | import { KindEthereumPersonalMessage, KindRawED25519, KindTezos } from './consts'; 4 | import { verifyEthereumPersonalMessage } from './eth_personal'; 5 | import { verifyRawEd25519 } from './raw_ed25519'; 6 | import { verifyTezosSignedMessage } from './tezos_micheline'; 7 | 8 | export function verify( 9 | signingMethod: string, 10 | verifyingKey: PublicKey, 11 | message: string, 12 | signature: Uint8Array, 13 | ): Promise { 14 | switch (signingMethod) { 15 | case KindEthereumPersonalMessage: 16 | return verifyEthereumPersonalMessage(verifyingKey, decodeUtf8(message), signature); 17 | case KindRawED25519: 18 | return verifyRawEd25519(verifyingKey, decodeUtf8(message), signature); 19 | case KindTezos: 20 | return verifyTezosSignedMessage(verifyingKey, message, signature); 21 | default: 22 | throw new Error(`Signing method ${signingMethod} not supported`); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/ethereum/address.ts: -------------------------------------------------------------------------------- 1 | import { KindSECP256K1, PublicKey } from '@mailchain/crypto'; 2 | import { decodeHexZeroX } from '@mailchain/encoding'; 3 | 4 | /** 5 | * Derive the ethereum address corresponding to the {@link PublicKey}. 6 | * 7 | * @param publicKey must be with with {@link KindSECP256K1} curve 8 | * @throw if the provided key is on unsupported curve 9 | */ 10 | export async function addressFromPublicKey(publicKey: PublicKey): Promise { 11 | if (publicKey.curve !== KindSECP256K1) { 12 | throw new Error(`public key must be ${KindSECP256K1}`); 13 | } 14 | const { computeAddress } = await import('@ethersproject/transactions'); 15 | 16 | return decodeHexZeroX(computeAddress(publicKey.bytes)); 17 | } 18 | 19 | export function validateEthereumAddress(address: string): boolean { 20 | return /^0x[a-fA-F0-9]{40}/.test(address); 21 | } 22 | 23 | export function validateEthereumTokenOwnerAddress(address: string): boolean { 24 | return /^[0-9]*.0x[a-fA-F0-9]{40}/.test(address); 25 | } 26 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/__snapshots__/presentation.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createPresentationPayload create 1`] = ` 4 | { 5 | "@context": [ 6 | "https://www.w3.org/2018/credentials/v1", 7 | ], 8 | "expirationDate": undefined, 9 | "holder": "did:mailchain:holder", 10 | "issuanceDate": "Sat, 01 Feb 2020 00:00:00 GMT", 11 | "requestId": "request-id", 12 | "type": [ 13 | "VerifiablePresentation", 14 | ], 15 | "verifiableCredential": [ 16 | { 17 | "@context": [ 18 | "https://www.w3.org/2018/credentials/v1", 19 | ], 20 | "credentialSubject": { 21 | "id": "credentialSubjectId", 22 | }, 23 | "issuanceDate": "2000-02-01T00:00:00.000Z", 24 | "issuer": { 25 | "id": "did:mailchain:issuer", 26 | }, 27 | "proof": { 28 | "id": "proof", 29 | }, 30 | "type": [ 31 | "type", 32 | ], 33 | }, 34 | ], 35 | "verifier": "did:mailchain:verifier", 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/filecoin/const.ts: -------------------------------------------------------------------------------- 1 | import { SECP256K1PrivateKey } from '@mailchain/crypto'; 2 | import { decodeHex } from '@mailchain/encoding'; 3 | 4 | // Alice 5 | export const AliceFilStr = 'f410faiikgixcdd7alrsbpunjlen4etkcarbfbghn7wi'; 6 | export const AliceFilEthAddressStr = '0x0210a322e218fe05c6417d1a9591bc24d4204425'; 7 | export const AliceFilEthPrivateKeyStr = '6c115551accbd74b46401a531af1dd0890034c18137dddab52a53c626e75b051'; 8 | export const AliceFilEthPrivateKey = new SECP256K1PrivateKey(decodeHex(AliceFilEthPrivateKeyStr)); 9 | 10 | // Bob 11 | export const BobFilAddressStr = 'f410f5blfdi7tmto6o2aa2up4vwtdm7qcnaiqpeazuwy'; 12 | export const BobFilEthAddressStr = '0xe85651a3f364dde76800d51fcada6367e0268110'; 13 | export const BobFilEthPrivateKeyStr = '2c6b29d5545e5804b7fb7e65404782e57dddea84cbcec9b2586add6403808965'; 14 | export const BobFilEthPrivateKey = new SECP256K1PrivateKey(decodeHex(BobFilEthPrivateKeyStr)); 15 | 16 | export const FILECOIN_PREFIX_F4_ETHEREUM = 'f410'; 17 | export const FILECOIN_PREFIX_T4_ETHEREUM = 't410'; 18 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/__snapshots__/resolver.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MailchainDIDMessagingKeyResolver should resolve: result 1`] = ` 4 | { 5 | "didDocument": { 6 | "authentication": [ 7 | { 8 | "controller": "did:mailchain:0xD5ab4CE3605Cd590Db609b6b5C8901fdB2ef7FE6@ethereum.mailchain.com", 9 | "id": "did:mailchain:0xD5ab4CE3605Cd590Db609b6b5C8901fdB2ef7FE6@ethereum.mailchain.com/messaging-key", 10 | "publicKeyHex": "723caa23a5b511af5ad7b7ef6076e414ab7e75a9dc910ea60e417a2b770a5671", 11 | "type": "ED25519SignatureVerification", 12 | }, 13 | ], 14 | "id": "did:mailchain:0xD5ab4CE3605Cd590Db609b6b5C8901fdB2ef7FE6@ethereum.mailchain.com", 15 | "verificationMethod": [], 16 | }, 17 | "didDocumentMetadata": { 18 | "canonicalId": "did:mailchain:0xD5ab4CE3605Cd590Db609b6b5C8901fdB2ef7FE6@ethereum.mailchain.com", 19 | }, 20 | "didResolutionMetadata": { 21 | "contentType": "application/did+ld+json", 22 | }, 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /packages/message-composer/src/headerOrder.ts: -------------------------------------------------------------------------------- 1 | import { HEADER_LABELS } from './consts'; 2 | import { Header } from './types'; 3 | 4 | /** Definition for the ordering the the headers. It is not something required by specification, but looks prettier. */ 5 | export const HEADER_ORDERS: { [key: string]: number | undefined } = { 6 | [HEADER_LABELS.MimeVersion]: 100, 7 | [HEADER_LABELS.Date]: 90, 8 | [HEADER_LABELS.MessageId]: 80, 9 | [HEADER_LABELS.Subject]: 50, 10 | [HEADER_LABELS.From]: 34, 11 | [HEADER_LABELS.ReplyTo]: 33, 12 | [HEADER_LABELS.To]: 32, 13 | [HEADER_LABELS.Cc]: 31, 14 | [HEADER_LABELS.Bcc]: 30, 15 | [HEADER_LABELS.ContentType]: -100, 16 | [HEADER_LABELS.ContentDisposition]: -101, 17 | [HEADER_LABELS.ContentTransferEncoding]: -102, 18 | [HEADER_LABELS.ContentId]: -103, 19 | } as const; 20 | 21 | /** Compares two headers for their ordering by using the {@link HEADER_ORDERS}. */ 22 | export function byHeaderOrder(a: Header, b: Header): number { 23 | return (HEADER_ORDERS[b.label] ?? 0) - (HEADER_ORDERS[a.label] ?? 0); 24 | } 25 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/index.ts: -------------------------------------------------------------------------------- 1 | export { MailSender } from '@mailchain/internal/sending/mail'; 2 | export { getMessagingKeyLatestNonce } from './addressNonce'; 3 | export { resolveAddress } from './resolvers'; 4 | export { getPrivateMessagingKey } from './privateMessagingKey'; 5 | export type { ResolvedAddress, ResolveAddressError, ResolveAddressResult } from '@mailchain/internal/messagingKeys'; 6 | export * from './keys'; 7 | 8 | // VC Request 9 | export { MailchainAddressOwnershipVerifier } from '@mailchain/internal/verifiableCredentials'; 10 | export { VerifiablePresentationRequestSender } from '@mailchain/internal/sending/verifiablePresentationRequest'; 11 | export type { 12 | VerifiablePresentationRequest, 13 | CredentialPayloadType, 14 | VerifyMailchainAddressOwnershipParams, 15 | VerifyMailchainAddressOwnershipError, 16 | } from '@mailchain/internal/verifiableCredentials'; 17 | export type { 18 | SentVerifiablePresentationRequest, 19 | SendVerifiablePresentationRequestError, 20 | } from '@mailchain/internal/sending/verifiablePresentationRequest'; 21 | -------------------------------------------------------------------------------- /packages/crypto/src/public.ts: -------------------------------------------------------------------------------- 1 | export interface PublicKey { 2 | readonly bytes: Uint8Array; 3 | verify: (message: Uint8Array, sig: Uint8Array) => Promise; 4 | curve: string; 5 | } 6 | 7 | /** 8 | * Compares two {@link PublicKey} objects for equality. 9 | * @param a The first {@link PublicKey} to compare. 10 | * @param b The second {@link PublicKey} to compare. 11 | * @returns True if both {@link PublicKey} are equal, false otherwise. 12 | */ 13 | export function isPublicKeyEqual(a: PublicKey, b: PublicKey): boolean { 14 | if (a.curve !== b.curve) { 15 | return false; 16 | } 17 | 18 | if (a.bytes.length !== a.bytes.length) { 19 | return false; 20 | } 21 | 22 | return a.bytes.every((value, index) => value === b.bytes[index]); 23 | } 24 | 25 | export function isPublicKey(x: any): x is PublicKey { 26 | if (typeof x !== 'object') return false; 27 | if (!(x.bytes instanceof Uint8Array)) return false; 28 | if (!(x.verify instanceof Function)) return false; 29 | if (typeof x.curve !== 'string') return false; 30 | 31 | return true; 32 | } 33 | -------------------------------------------------------------------------------- /packages/encoding/src/base32.ts: -------------------------------------------------------------------------------- 1 | import { utils } from '@scure/base'; 2 | 3 | const BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567'; 4 | const coder = utils.chain( 5 | // We define our own chain, the default base32 has padding 6 | utils.radix2(5), 7 | utils.alphabet(BASE32_ALPHABET), 8 | { 9 | decode: (input) => input.split(''), 10 | encode: (input) => input.join(''), 11 | }, 12 | ); 13 | export function decodeBase32(input: string): Uint8Array { 14 | return coder.decode(input); 15 | } 16 | 17 | export function encodeBase32(input: Uint8Array): string { 18 | return coder.encode(input); 19 | } 20 | 21 | export function isBase32(input: string): boolean { 22 | // Check that all characters are valid base32 characters 23 | for (let i = 0; i < input.length; i++) { 24 | const char = input[i]; 25 | if (BASE32_ALPHABET.indexOf(char.toLowerCase()) === -1) { 26 | return false; 27 | } 28 | } 29 | 30 | // Check that the input length is a multiple of 8 bits 31 | if (input.length % 8 !== 0) { 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | -------------------------------------------------------------------------------- /packages/internal/src/messagingKeys/contractResolvers/resolver.ts: -------------------------------------------------------------------------------- 1 | import { ProtocolType } from '@mailchain/addressing'; 2 | import { ContractCall } from '@mailchain/api'; 3 | import { PublicKey } from '@mailchain/crypto'; 4 | import { MailchainResult } from '../../'; 5 | import { Proof } from '../proof'; 6 | import { InvalidContractResponseError, MessagingKeyNotFoundInContractError } from './errors'; 7 | 8 | export type ContractMessagingKey = { 9 | messagingKey: PublicKey; 10 | protocol: ProtocolType; 11 | proof: Proof; 12 | }; 13 | export type ContractMessagingKeyError = MessagingKeyNotFoundInContractError | InvalidContractResponseError; 14 | export type ContractCallResolveResult = MailchainResult< 15 | ContractMessagingKey, 16 | MessagingKeyNotFoundInContractError | InvalidContractResponseError 17 | >; 18 | 19 | export interface ContractCallMessagingKeyResolver { 20 | resolve(contract: ContractCall): Promise; 21 | } 22 | 23 | export interface ContractCallLatestNonce { 24 | latestNonce(contract: ContractCall): Promise; 25 | } 26 | -------------------------------------------------------------------------------- /packages/internal/src/sending/payload/create.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PrivateKey, BobED25519PrivateKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { createPayload } from './create'; 3 | 4 | describe('createPayload', () => { 5 | const content = Buffer.from('content'); 6 | 7 | beforeAll(() => { 8 | jest.useFakeTimers().setSystemTime(new Date('2022-06-06')); 9 | }); 10 | afterAll(() => { 11 | jest.useRealTimers(); 12 | }); 13 | it('should create a payload - alice key', async () => { 14 | expect( 15 | await createPayload( 16 | AliceED25519PrivateKey, 17 | content, 18 | 'application/vnd.mailchain.verified-credential-request', 19 | { plugin: { value: 'value' } }, 20 | ), 21 | ).toMatchSnapshot(); 22 | }); 23 | 24 | it('should create a payload - bob key', async () => { 25 | expect( 26 | await createPayload( 27 | BobED25519PrivateKey, 28 | content, 29 | 'application/vnd.mailchain.verified-credential-request', 30 | { plugin: { value: 'value' } }, 31 | ), 32 | ).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/crypto/src/mnemonic/mnemonic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | entropyToMnemonic, 3 | mnemonicToEntropy, 4 | generateMnemonic, 5 | validateMnemonic, 6 | mnemonicToSeedSync, 7 | } from '@scure/bip39'; 8 | import { wordlist } from '@scure/bip39/wordlists/english'; 9 | 10 | export function generate(words: 12 | 24 = 24): string { 11 | return generateMnemonic(wordlist, words === 12 ? 128 : 256); 12 | } 13 | 14 | export function toEntropy(mnemonic: string): Uint8Array { 15 | return mnemonicToEntropy(mnemonic, wordlist); 16 | } 17 | 18 | export function fromEntropy(entropy: Uint8Array): string { 19 | return entropyToMnemonic(entropy, wordlist); 20 | } 21 | 22 | export function validate(mnemonic: string): boolean { 23 | return validateMnemonic(mnemonic, wordlist); 24 | } 25 | 26 | export function toSeed(mnemonic: string, password?: string, byteLength?: 32 | 64 | undefined): Uint8Array { 27 | if (!validate(mnemonic)) { 28 | throw new Error('Invalid mnemonic'); 29 | } 30 | return mnemonicToSeedSync(mnemonic, password).slice(0, byteLength === undefined ? 32 : byteLength); 31 | } 32 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/api", 3 | "version": "0.31.0", 4 | "description": "Mailchain api tools", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "api" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@mailchain/crypto": "0.31.0", 21 | "@mailchain/encoding": "0.31.0", 22 | "@noble/hashes": "^1.3.0", 23 | "axios": "1.6.0", 24 | "canonicalize": "^1.0.8", 25 | "lodash": "^4.17.21" 26 | }, 27 | "devDependencies": { 28 | "@mailchain/keyring": "0.31.0", 29 | "axios-mock-adapter": "^1.21.1" 30 | }, 31 | "scripts": { 32 | "build": "run -T tsup", 33 | "test": "run -T jest", 34 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 35 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/crypto/src/multikey/names.ts: -------------------------------------------------------------------------------- 1 | import { KindSECP256K1, KindED25519, KindSECP256R1 } from '../keys'; 2 | import { PublicKey } from '../public'; 3 | import { SECP256K1PublicKey } from '../secp256k1/public'; 4 | import { ED25519PublicKey } from '../ed25519/public'; 5 | import { SECP256R1PublicKey } from '../secp256r1'; 6 | 7 | export function kindFromPublicKey(key: PublicKey): string { 8 | switch (key.constructor) { 9 | case SECP256K1PublicKey: 10 | return KindSECP256K1; 11 | case ED25519PublicKey: 12 | return KindED25519; 13 | case SECP256R1PublicKey: 14 | return KindSECP256R1; 15 | default: 16 | throw RangeError('unknown public key type'); 17 | } 18 | } 19 | 20 | export function publicKeyFromKind(kind: string, data: Uint8Array): PublicKey { 21 | switch (kind) { 22 | case KindSECP256K1: 23 | return new SECP256K1PublicKey(data); 24 | case KindED25519: 25 | return new ED25519PublicKey(data); 26 | case KindSECP256R1: 27 | return new SECP256R1PublicKey(data); 28 | default: 29 | throw RangeError('unknown public key type'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/addressing/src/addressFormatting.ts: -------------------------------------------------------------------------------- 1 | import { humanNameServiceFormatters } from './addressFormattingRule'; 2 | import { formatMailLike } from './formatMailLike'; 3 | import { isNameServiceAddress, NameServiceAddress as MailchainAddress, NameServiceAddress } from './nameServiceAddress'; 4 | 5 | export function formatAddress(address: MailchainAddress, format: 'mail' | 'human-friendly'): string { 6 | if (isNameServiceAddress(address)) return formatNameServiceAddress(address, format); 7 | throw new Error(`unknown format of the provided address [${JSON.stringify(address)}]`); 8 | } 9 | 10 | function formatNameServiceAddress(address: NameServiceAddress, format: 'mail' | 'human-friendly'): string { 11 | switch (format) { 12 | case 'mail': 13 | return formatMailLike(address.username, address.domain); 14 | case 'human-friendly': 15 | for (const formatter of humanNameServiceFormatters) { 16 | const formatted = formatter(address); 17 | if (formatted) return formatted; 18 | } 19 | throw new Error(`failed to format address [${JSON.stringify(address)}]`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/internal/src/sending/payload/create.ts: -------------------------------------------------------------------------------- 1 | import { KindNaClSecretKey, SignerWithPublicKey } from '@mailchain/crypto'; 2 | import { EncodingTypes } from '@mailchain/encoding'; 3 | import isArrayBuffer from 'lodash/isArrayBuffer.js'; 4 | import { PayloadHeaders } from '../../transport/payload/headers'; 5 | import { ContentType, Payload } from '../../transport'; 6 | 7 | export async function createPayload( 8 | signerMessagingKey: SignerWithPublicKey, 9 | content: Buffer | Uint8Array, 10 | contentType: C, 11 | pluginHeaders?: Record, 12 | ): Promise>> { 13 | return { 14 | Headers: { 15 | Origin: signerMessagingKey.publicKey, 16 | ContentSignature: await signerMessagingKey.sign(content), 17 | Created: new Date(), 18 | ContentLength: content.length, 19 | ContentType: contentType, 20 | ContentEncoding: EncodingTypes.Base64, 21 | ContentEncryption: KindNaClSecretKey, 22 | PluginHeaders: pluginHeaders, 23 | }, 24 | Content: isArrayBuffer(content) ? Buffer.from(content) : content, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/publicKeyDecrypter.ts: -------------------------------------------------------------------------------- 1 | import { EncryptedContent, PlainContent, Decrypter } from '../cipher'; 2 | import { KeyExchange } from '../keyExchange'; 3 | import { PrivateKey } from '../../private'; 4 | import { fromPrivateKey } from '../ecdh/ecdh'; 5 | import { easyOpen } from './secretbox'; 6 | import { deserializePublicKeyEncryptedContent } from './serialization'; 7 | 8 | export class PublicKeyDecrypter implements Decrypter { 9 | private _keyEx: KeyExchange; 10 | private _prvKey: PrivateKey; 11 | 12 | constructor(keyEx: KeyExchange, prvKey: PrivateKey) { 13 | this._keyEx = keyEx; 14 | this._prvKey = prvKey; 15 | } 16 | static FromPrivateKey(key: PrivateKey): PublicKeyDecrypter { 17 | return new this(fromPrivateKey(key), key); 18 | } 19 | 20 | async decrypt(input: EncryptedContent): Promise { 21 | const secretData = deserializePublicKeyEncryptedContent(input); 22 | 23 | const sharedSecret = await this._keyEx.SharedSecret(this._prvKey, secretData.pubKey); 24 | 25 | return easyOpen(secretData.encryptedContent, sharedSecret); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/__snapshots__/payload.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createVerifiableCredential create - ownerOf 1`] = ` 4 | { 5 | "@context": [ 6 | "https://www.w3.org/2018/credentials/v1", 7 | ], 8 | "credentialSubject": [ 9 | { 10 | "ownerOf": { 11 | "address": "address", 12 | "type": "MailchainMessagingKey", 13 | }, 14 | }, 15 | ], 16 | "issuanceDate": "2000-02-01T00:00:00.000Z", 17 | "issuer": { 18 | "id": "did:mailchain:issuer", 19 | }, 20 | "proof": { 21 | "type": "proof-type", 22 | }, 23 | "termsOfUse": [ 24 | { 25 | "actions": [ 26 | "action", 27 | ], 28 | "assignee": "did:mailchain:assignee", 29 | "assigner": "did:mailchain:assigner", 30 | "effect": "Allow", 31 | "id": "terms-1", 32 | "resources": [ 33 | "*", 34 | ], 35 | "type": "HolderPolicy", 36 | }, 37 | ], 38 | "type": [ 39 | "VerifiableCredential", 40 | "MailchainMessagingKeyCredential", 41 | ], 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /packages/internal/src/formatters/generate.test.ts: -------------------------------------------------------------------------------- 1 | import { dummyMailData, dummyMailDataResolvedAddresses } from '../test.const'; 2 | import { createMimeMessage } from './generate'; 3 | 4 | describe('createMimeMessage', () => { 5 | it('should generate mail message', async () => { 6 | const messages = await createMimeMessage(dummyMailData, dummyMailDataResolvedAddresses); 7 | 8 | expect(removeRandomBoundaries(messages.original)).toMatchSnapshot('ORIGINAL'); 9 | expect(removeRandomBoundaries(messages.visibleRecipients)).toMatchSnapshot('VISIBLE'); 10 | expect(removeRandomBoundaries(messages.blindRecipients[0].content)).toMatchSnapshot( 11 | `BLIND ${dummyMailData.blindCarbonCopyRecipients[0].address}`, 12 | ); 13 | expect(removeRandomBoundaries(messages.blindRecipients[1].content)).toMatchSnapshot( 14 | `BLIND ${dummyMailData.blindCarbonCopyRecipients[1].address}`, 15 | ); 16 | }); 17 | }); 18 | 19 | function removeRandomBoundaries(msg: string): string { 20 | msg = msg.replaceAll(/boundary.*/g, `boundary="boundary"`); 21 | msg = msg.replaceAll(/--.*/g, `--boundary`); 22 | 23 | return msg; 24 | } 25 | -------------------------------------------------------------------------------- /packages/keyring/src/functions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Decrypter, 3 | ED25519KeyExchange, 4 | EncryptedContent, 5 | Encrypter, 6 | PlainContent, 7 | PrivateKey, 8 | PrivateKeyDecrypter, 9 | PublicKey, 10 | SignerWithPublicKey, 11 | ED25519PrivateKey, 12 | } from '@mailchain/crypto'; 13 | 14 | export interface KeyRingDecrypter extends SignerWithPublicKey { 15 | ecdhDecrypt(bundleEphemeralKey: PublicKey, input: EncryptedContent): Promise; 16 | } 17 | 18 | export type InboxKey = Encrypter & Decrypter; 19 | 20 | export function ecdhKeyRingDecrypter(privateKey: PrivateKey): KeyRingDecrypter { 21 | return { 22 | curve: privateKey.curve, 23 | sign: (input) => privateKey.sign(input), 24 | publicKey: privateKey.publicKey, 25 | ecdhDecrypt: async (bundleEphemeralKey: PublicKey, input: Uint8Array) => { 26 | const keyEx = new ED25519KeyExchange(); 27 | const sharedSecret = await keyEx.SharedSecret(privateKey, bundleEphemeralKey); 28 | const decrypter = PrivateKeyDecrypter.fromPrivateKey(ED25519PrivateKey.fromSeed(sharedSecret)); 29 | 30 | return decrypter.decrypt(input); 31 | }, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/crypto", 3 | "version": "0.31.0", 4 | "license": "Apache-2.0", 5 | "dependencies": { 6 | "@ethersproject/hash": "5.7.0", 7 | "@ethersproject/signing-key": "5.6.2", 8 | "@mailchain/encoding": "0.31.0", 9 | "@noble/curves": "^0.8.2", 10 | "@noble/hashes": "^1.3.0", 11 | "@scure/bip39": "^1.2.0", 12 | "bn.js": "^5.2.1", 13 | "ed2curve": "^0.3.0", 14 | "secp256k1": "^4.0.2", 15 | "tweetnacl": "^1.0.3" 16 | }, 17 | "devDependencies": { 18 | "@types/bn.js": "^5.1.0", 19 | "@types/ed2curve": "^0.2.3", 20 | "@types/secp256k1": "^4.0.2" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 25 | "directory": "crypto" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 29 | }, 30 | "engines": { 31 | "node": ">=15.0.0" 32 | }, 33 | "scripts": { 34 | "build": "run -T tsup", 35 | "test": "run -T jest", 36 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 37 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/internal/src/transport/verifier/sender.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, isPublicKeyEqual } from '@mailchain/crypto'; 2 | import { Configuration } from '../../configuration'; 3 | import { MessagingKeys } from '../../messagingKeys'; 4 | 5 | export class SenderVerifier { 6 | constructor(private readonly messagingKeys: MessagingKeys) {} 7 | 8 | static create(configuration: Configuration) { 9 | return new SenderVerifier(MessagingKeys.create(configuration)); 10 | } 11 | /** 12 | * 13 | * @param fromAddress address that sent the mail. `From:` header in the mail. 14 | * @param senderMessagingKey public key of the sender. 15 | * @param at Date to resolve the sender messaging key. When no date is provided, the address resolves using the latest block. 16 | * @returns 17 | */ 18 | async verifySenderOwnsFromAddress(fromAddress: string, senderMessagingKey: PublicKey, at?: Date): Promise { 19 | const { data, error } = await this.messagingKeys.resolveIndividual(fromAddress, at); 20 | if (error != null) { 21 | return false; 22 | } 23 | 24 | return isPublicKeyEqual(data.messagingKey, senderMessagingKey); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/internal/src/user/consolidateMailbox.ts: -------------------------------------------------------------------------------- 1 | import { isSameAddress } from '@mailchain/addressing'; 2 | import uniqWith from 'lodash/unionWith.js'; 3 | import { Alias } from './types'; 4 | import { NewUserMailbox } from './userProfile'; 5 | 6 | export function consolidateMailbox(mailbox: NewUserMailbox): NewUserMailbox { 7 | const consolidators = [consolidateMailboxLabel, consolidateMailboxAliases]; 8 | return consolidators.reduce((prev, consolidator) => consolidator(prev), mailbox); 9 | } 10 | 11 | export function consolidateMailboxLabel(mailbox: NewUserMailbox): NewUserMailbox { 12 | return { ...mailbox, label: mailbox.label?.trim() || null }; 13 | } 14 | 15 | /** 16 | * Consolidate the provided mailbox aliases with the following rules: 17 | * - There can be only single alias per `address`. If there are duplicates, only the first one in the entries is kept, the others are ignored. 18 | */ 19 | export function consolidateMailboxAliases(mailbox: NewUserMailbox): NewUserMailbox { 20 | return { 21 | ...mailbox, 22 | aliases: uniqWith(mailbox.aliases, (a, b) => isSameAddress(a.address, b.address)) as [Alias, ...Alias[]], 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/internal/src/transport/deliveryRequests/keybundle.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import { PublicKey, RandomFunction, secureRandom, publicKeyToBytes, ED25519KeyExchange } from '@mailchain/crypto'; 3 | import { protocol } from '../../protobuf/protocol/protocol'; 4 | 5 | export async function createECDHKeyBundle( 6 | recipientMessagingKey: PublicKey, 7 | rand: RandomFunction = secureRandom, 8 | ): Promise<{ 9 | keyBundle: protocol.ECDHKeyBundle; 10 | secret: Uint8Array; 11 | }> { 12 | const keyEx = new ED25519KeyExchange(rand); 13 | const ephemeralKey = await keyEx.EphemeralKey(); 14 | const sharedSecret = await keyEx.SharedSecret(ephemeralKey, recipientMessagingKey); 15 | 16 | const payload = { 17 | publicMessagingKey: publicKeyToBytes(recipientMessagingKey), 18 | publicEphemeralKey: publicKeyToBytes(ephemeralKey.publicKey), 19 | } as protocol.IECDHKeyBundle; 20 | 21 | const errMsg = protocol.ECDHKeyBundle.verify(payload); 22 | if (errMsg) { 23 | throw Error(errMsg); 24 | } 25 | 26 | return { 27 | secret: sharedSecret, 28 | keyBundle: protocol.ECDHKeyBundle.create(payload), 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/signatures/src/mailchain_username.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Signer } from '@mailchain/crypto'; 2 | import { signRawEd25519, verifyRawEd25519 } from './raw_ed25519'; 3 | 4 | const mailchainUsernameMessage = function (message: Uint8Array) { 5 | const prefix = Buffer.from(`\x11Mailchain username ownership:\n${message.length}\n`, 'utf-8'); 6 | return Buffer.concat([prefix, message]); 7 | }; 8 | 9 | /** 10 | * Signs a message using the Mailchain username with the identity private key. 11 | * @param key 12 | * @param username 13 | */ 14 | export async function signMailchainUsername(signer: Signer, username: Uint8Array): Promise { 15 | return signRawEd25519(signer, mailchainUsernameMessage(username)); 16 | } 17 | 18 | /** 19 | * Verifies a message linking a username with an identity key is valid. 20 | * @param key 21 | * @param username 22 | * @param signature 23 | * @returns 24 | */ 25 | export async function verifyMailchainUsername( 26 | key: PublicKey, 27 | signature: Uint8Array, 28 | username: Uint8Array, 29 | ): Promise { 30 | return verifyRawEd25519(key, mailchainUsernameMessage(username), signature); 31 | } 32 | -------------------------------------------------------------------------------- /packages/signatures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/signatures", 3 | "version": "0.31.0", 4 | "description": "Mailchain signatures", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "signatures" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@ethersproject/hash": "5.7.0", 21 | "@mailchain/addressing": "0.31.0", 22 | "@mailchain/crypto": "0.31.0", 23 | "@mailchain/encoding": "0.31.0", 24 | "@noble/hashes": "^1.3.0", 25 | "canonicalize": "^1.0.8", 26 | "lodash": "^4.17.21" 27 | }, 28 | "devDependencies": { 29 | "@mailchain/addressing": "0.31.0", 30 | "@mailchain/keyring": "0.31.0" 31 | }, 32 | "scripts": { 33 | "build": "run -T tsup", 34 | "test": "run -T jest", 35 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 36 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/signatures/src/eth_personal.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, PrivateKey, KindSECP256K1, ErrorUnsupportedKey } from '@mailchain/crypto'; 2 | import { decodeHexZeroX } from '@mailchain/encoding'; 3 | 4 | export async function verifyEthereumPersonalMessage( 5 | key: PublicKey, 6 | message: Uint8Array, 7 | signature: Uint8Array, 8 | ): Promise { 9 | switch (key.curve) { 10 | case KindSECP256K1: 11 | const messageHash = await getMessageHash(message); 12 | return key.verify(messageHash, signature); 13 | default: 14 | throw new ErrorUnsupportedKey(key.curve); 15 | } 16 | } 17 | 18 | export async function signEthereumPersonalMessage(key: PrivateKey, message: Uint8Array): Promise { 19 | switch (key.curve) { 20 | case KindSECP256K1: 21 | const messageHash = await getMessageHash(message); 22 | 23 | return key.sign(messageHash); 24 | default: 25 | throw new ErrorUnsupportedKey(key.curve); 26 | } 27 | } 28 | 29 | export async function getMessageHash(message: Uint8Array): Promise { 30 | const { hashMessage } = await import('@ethersproject/hash'); 31 | return decodeHexZeroX(hashMessage(message)); 32 | } 33 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/messageId.ts: -------------------------------------------------------------------------------- 1 | import { publicKeyToBytes, PublicKey } from '@mailchain/crypto'; 2 | import { decodeUtf8, encodeHex } from '@mailchain/encoding'; 3 | import { KeyRing } from '@mailchain/keyring'; 4 | import { sha3_256 } from '@noble/hashes/sha3'; 5 | import { MailData } from '../transport'; 6 | 7 | export type MessageIdCreator = ( 8 | params: 9 | | { mailData: MailData; type: 'sent' } 10 | | { mailData: MailData; type: 'received'; mailbox: PublicKey; owner: string }, 11 | ) => Promise; 12 | 13 | export function createMailchainMessageIdCreator(keyRing: KeyRing): MessageIdCreator { 14 | return async (params) => { 15 | const typeBytes = decodeUtf8(params.type); 16 | const mailIdBytes = decodeUtf8(params.mailData.id); 17 | const mailboxBytes = params.type === 'received' ? publicKeyToBytes(params.mailbox) : new Uint8Array(); 18 | const ownerBytes = params.type === 'received' ? decodeUtf8(params.owner) : new Uint8Array(); 19 | 20 | return encodeHex( 21 | sha3_256( 22 | await keyRing 23 | .rootInboxKey() 24 | .sign(Uint8Array.from([...typeBytes, ...mailIdBytes, ...mailboxBytes, ...ownerBytes])), 25 | ), 26 | ); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/sdk", 3 | "version": "0.31.0", 4 | "description": "Mailchain sdk for sending messages to web3 addresses", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "sdk" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "homepage": "https://docs.mailchain.com/developer", 20 | "dependencies": { 21 | "@mailchain/addressing": "0.31.0", 22 | "@mailchain/crypto": "0.31.0", 23 | "@mailchain/encoding": "0.31.0", 24 | "@mailchain/internal": "0.31.0", 25 | "@mailchain/keyring": "0.31.0" 26 | }, 27 | "engines": { 28 | "node": ">=15.0.0" 29 | }, 30 | "scripts": { 31 | "build": "run -T tsup", 32 | "test": "run -T jest --passWithNoTests", 33 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 34 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 35 | }, 36 | "devDependencies": { 37 | "@peculiar/webcrypto": "^1.4.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/ethereum/address.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { AliceSECP256K1PublicKey, BobSECP256K1PublicKey } from '@mailchain/crypto/secp256k1/test.const'; 3 | import { AliceSECP256K1PublicAddress, BobSECP256K1PublicAddress } from './test.const'; 4 | import { addressFromPublicKey } from './address'; 5 | 6 | describe('addressFromPublicKey', () => { 7 | const tests = [ 8 | { 9 | name: 'alice', 10 | publicKey: AliceSECP256K1PublicKey, 11 | expected: AliceSECP256K1PublicAddress, 12 | shouldThrow: false, 13 | }, 14 | { 15 | name: 'bob', 16 | publicKey: BobSECP256K1PublicKey, 17 | expected: BobSECP256K1PublicAddress, 18 | shouldThrow: false, 19 | }, 20 | { 21 | name: 'incorrect key', 22 | publicKey: AliceED25519PublicKey, 23 | shouldThrow: true, 24 | }, 25 | ]; 26 | test.each(tests)('$name', async (test) => { 27 | if (test.shouldThrow) { 28 | expect(async () => { 29 | await addressFromPublicKey(test.publicKey); 30 | }).rejects.toThrow(); 31 | } else { 32 | expect(await addressFromPublicKey(test.publicKey)).toEqual(test.expected); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/addressing/src/addressFromPublicKey.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@mailchain/crypto'; 2 | import { ETHEREUM, ProtocolType, SOLANA, TEZOS } from './protocols'; 3 | import { addressFromPublicKey as ethereumAddressFromPublicKey } from './protocols/ethereum/address'; 4 | import { tezosAddressFromPublicKey } from './protocols/tezos/address'; 5 | import { solanaAddressFromPublicKey } from './protocols/solana/address'; 6 | 7 | /** 8 | * Derive the address corresponding to the {@link PublicKey}. 9 | * 10 | * @param publicKey the key to derive the address from 11 | * @param protocol the protocol the address should be derived by 12 | * 13 | * @throws Error for unsupported protocols and for protocol unsupported key types. 14 | */ 15 | export async function addressFromPublicKey(publicKey: PublicKey, protocol: ProtocolType): Promise { 16 | switch (protocol) { 17 | case ETHEREUM: 18 | return ethereumAddressFromPublicKey(publicKey); 19 | case TEZOS: 20 | return tezosAddressFromPublicKey(publicKey); 21 | case SOLANA: 22 | return solanaAddressFromPublicKey(publicKey); 23 | default: 24 | throw new Error(`address from PublicKey for {${protocol}} not unsupported`); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/crypto/src/scrypt/scrypt.ts: -------------------------------------------------------------------------------- 1 | import { scrypt } from '@noble/hashes/scrypt'; 2 | import { secureRandom } from '../rand'; 3 | 4 | export type ScryptParams = { 5 | N: number; 6 | r: number; 7 | p: number; 8 | keyLen: number; 9 | }; 10 | 11 | export const defaultScryptParams: ScryptParams = { 12 | // N is the N parameter of Scrypt encryption algorithm, using 256MB 13 | // memory and taking approximately 1s CPU time on a modern processor. 14 | N: 1 << 18, 15 | // P is the P parameter of Scrypt encryption algorithm, using 256MB 16 | // memory and taking approximately 1s CPU time on a modern processor. 17 | p: 1, 18 | r: 8, 19 | keyLen: 32, 20 | }; 21 | 22 | interface Result { 23 | params: ScryptParams; 24 | secret: Uint8Array; 25 | salt: Uint8Array; 26 | } 27 | 28 | export function deriveSecretFromScrypt( 29 | passphrase = '', 30 | params: ScryptParams = defaultScryptParams, 31 | salt: Uint8Array = secureRandom(32), 32 | ): Result { 33 | const secret = scrypt(new Uint8Array(Buffer.from(passphrase, 'ascii')), salt, { 34 | N: params.N, 35 | r: params.r, 36 | p: params.p, 37 | dkLen: params.keyLen, 38 | }); 39 | 40 | return { 41 | params, 42 | secret, 43 | salt, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/parseVerifiablePresentationRequest.test.ts: -------------------------------------------------------------------------------- 1 | import canonicalize from 'canonicalize'; 2 | import { parseVerifiablePresentationRequest } from './parseVerifiablePresentationRequest'; 3 | import { VerifiablePresentationRequest } from './request'; 4 | 5 | const originalVcRequest: VerifiablePresentationRequest = { 6 | type: 'MailchainMessagingKeyCredential', 7 | requestId: 'request-id', 8 | version: '1.0', 9 | from: 'example-app@mailchain.test', 10 | to: 'alice@mailhain.test', 11 | actions: ['Authenticate', 'Join Meeting'], 12 | resources: ['meeting/*'], 13 | signedCredentialExpiresAt: new Date('2020-02-03T00:00:00.000Z'), 14 | signedCredentialExpiresAfter: 3600, 15 | requestExpiresAfter: new Date('2020-02-02T00:00:00.000Z'), 16 | nonce: '1234', 17 | approvedCallback: { 18 | url: 'https://example.com/callback', 19 | }, 20 | }; 21 | 22 | describe('parseVerifiablePresentationRequest', () => { 23 | it('should parse a verifiable presentation request', async () => { 24 | const json = canonicalize(originalVcRequest)!; 25 | 26 | const vcRequest = parseVerifiablePresentationRequest(json); 27 | 28 | expect(vcRequest).toEqual(originalVcRequest); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/consts.ts: -------------------------------------------------------------------------------- 1 | // WARNING: CAREFUL WHEN CHANGING THE VALUES OF THIS CONSTANTS, THEY MIGHT BE ALREADY LINKED TO KEY PATHS 2 | 3 | /** 4 | * Algorand protocol name. 5 | */ 6 | export const ALGORAND = 'algorand' as const; 7 | /** 8 | * Ethereum protocol name. 9 | */ 10 | export const ETHEREUM = 'ethereum' as const; 11 | /** 12 | * Substrate protocol name. 13 | */ 14 | export const SUBSTRATE = 'substrate' as const; 15 | 16 | export const NEAR = 'near' as const; 17 | 18 | export const TEZOS = 'tezos' as const; 19 | 20 | export const SOLANA = 'solana' as const; 21 | 22 | /** 23 | * Mailchain protocol name. 24 | */ 25 | export const MAILCHAIN = 'mailchain' as const; 26 | 27 | export const ALL_PROTOCOLS = [ALGORAND, ETHEREUM, SUBSTRATE, NEAR, MAILCHAIN, TEZOS, SOLANA] as const; 28 | export type ProtocolType = (typeof ALL_PROTOCOLS)[number]; 29 | 30 | const ENABLED_PROTOCOLS = [ETHEREUM, NEAR, TEZOS, SOLANA] as const; 31 | export type EnabledBlockchainProtocol = (typeof ENABLED_PROTOCOLS)[number]; 32 | export function isBlockchainProtocolEnabled(protocol: string): protocol is EnabledBlockchainProtocol { 33 | return ENABLED_PROTOCOLS.includes(protocol as EnabledBlockchainProtocol); 34 | } 35 | -------------------------------------------------------------------------------- /packages/crypto/src/ed25519/hd.test.ts: -------------------------------------------------------------------------------- 1 | import { UnknownPrivateKey } from '../testing'; 2 | import { AliceED25519PrivateKey, BobED25519PrivateKey } from './test.const'; 3 | import { ED25519ExtendedPrivateKey } from '.'; 4 | 5 | describe('FromPrivateKey()', () => { 6 | const tests = [ 7 | { 8 | name: 'alice', 9 | arg: AliceED25519PrivateKey, 10 | expected: { 11 | bytes: AliceED25519PrivateKey.bytes, 12 | privateKey: AliceED25519PrivateKey, 13 | } as ED25519ExtendedPrivateKey, 14 | shouldThrow: false, 15 | }, 16 | { 17 | name: 'bob', 18 | arg: BobED25519PrivateKey, 19 | expected: { 20 | bytes: BobED25519PrivateKey.bytes, 21 | privateKey: BobED25519PrivateKey, 22 | } as ED25519ExtendedPrivateKey, 23 | shouldThrow: false, 24 | }, 25 | { 26 | name: 'invalid', 27 | arg: new UnknownPrivateKey(), 28 | expected: null, 29 | shouldThrow: true, 30 | }, 31 | ]; 32 | test.each(tests)('$name', async (test) => { 33 | if (test.shouldThrow) { 34 | expect(() => { 35 | ED25519ExtendedPrivateKey.fromPrivateKey(test.arg); 36 | }).toThrow(); 37 | } else { 38 | expect(ED25519ExtendedPrivateKey.fromPrivateKey(test.arg)).toEqual(test.expected); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/crypto/src/keys.ts: -------------------------------------------------------------------------------- 1 | // KindSECP256K1 string identifier for secp256k1 keys. 2 | export const KindSECP256K1 = 'secp256k1'; 3 | // KindED25519 string identifier for ed25519 keys. 4 | export const KindED25519 = 'ed25519'; 5 | // KindSECP256R1 string identifier for P256 keys. 6 | export const KindSECP256R1 = 'secp256r1'; 7 | // KindSR25519 string identifier for sr25519 keys. 8 | export const KindSR25519 = 'sr25519'; 9 | // KindNoOp string identifier for no-operation keys. 10 | export const KindNoOp = 'noop'; 11 | 12 | // IdSECP256K1 Id identifier for secp256k1 keys. 13 | export const IdSECP256K1 = 0xe1; 14 | // IdED25519 Id identifier for ed25519 keys. 15 | export const IdED25519 = 0xe2; 16 | // IdSR25519 Id identifier for sr25519 keys. 17 | export const IdSR25519 = 0xe3; 18 | // IdSECP256R1 Id identifier for SECP256R1 keys. 19 | export const IdSECP256R1 = 0xe4; 20 | 21 | // IdNonSpecified Id identifier for non specified secret keys. 22 | export const IdNonSpecified = 0xee; 23 | 24 | export type KeyKinds = typeof KindED25519 | typeof KindSECP256K1 | typeof KindSR25519 | typeof KindSECP256R1; 25 | 26 | export enum CurveIds { 27 | SECP256K1 = IdSECP256K1, 28 | ED25519 = IdED25519, 29 | SR25519 = IdSR25519, 30 | SECP256R1 = IdSECP256R1, 31 | } 32 | -------------------------------------------------------------------------------- /packages/internal/src/transport/serialization/chunk.test.ts: -------------------------------------------------------------------------------- 1 | import { chunkBuffer } from './chunk'; 2 | 3 | describe('chunkBuffer', () => { 4 | const tests = [ 5 | { 6 | name: 'Uint8Array', 7 | input: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 8 | size: 3, 9 | expected: [ 10 | Buffer.from(new Uint8Array([0, 1, 2])), 11 | Buffer.from(new Uint8Array([3, 4, 5])), 12 | Buffer.from(new Uint8Array([6, 7, 8])), 13 | Buffer.from(new Uint8Array([9, 10])), 14 | ], 15 | shouldThrow: false, 16 | }, 17 | { 18 | name: 'Buffer', 19 | input: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 20 | size: 3, 21 | expected: [ 22 | Buffer.from(new Uint8Array([0, 1, 2])), 23 | Buffer.from(new Uint8Array([3, 4, 5])), 24 | Buffer.from(new Uint8Array([6, 7, 8])), 25 | Buffer.from(new Uint8Array([9, 10])), 26 | ], 27 | shouldThrow: false, 28 | }, 29 | ]; 30 | tests.forEach((test) => { 31 | it(test.name, () => { 32 | const target = chunkBuffer; 33 | 34 | if (test.shouldThrow) { 35 | expect(() => { 36 | target(test.input, test.size); 37 | }).toThrow(); 38 | } else { 39 | expect(target(test.input, test.size)).toEqual(test.expected); 40 | } 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/crypto/src/multikey/bytes.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '../public'; 2 | import { PrivateKey } from '../private'; 3 | import { idFromPrivateKey, idFromPublicKey, privateKeyFromId, publicKeyFromId } from './ids'; 4 | 5 | export function publicKeyToBytes(key: PublicKey): Uint8Array { 6 | const out = new Uint8Array(key.bytes.length + 1); 7 | 8 | out[0] = idFromPublicKey(key); 9 | out.set(key.bytes, 1); 10 | 11 | return out; 12 | } 13 | 14 | export function publicKeyFromBytes(bytes: Uint8Array | Buffer): PublicKey { 15 | if (bytes.length < 32 + 1) { 16 | throw Error('public key is too short to contain ID byte'); 17 | } 18 | // create a new Uint8Array object as it bytes might be a buffer 19 | return publicKeyFromId(bytes[0], new Uint8Array(bytes.slice(1))); 20 | } 21 | 22 | export function privateKeyToBytes(key: PrivateKey): Uint8Array { 23 | const out = new Uint8Array(key.bytes.length + 1); 24 | 25 | out[0] = idFromPrivateKey(key); 26 | out.set(key.bytes, 1); 27 | 28 | return out; 29 | } 30 | 31 | export function privateKeyFromBytes(bytes: Uint8Array): PrivateKey { 32 | if (bytes.length < 32 + 1) { 33 | throw Error('private key is too short to contain ID byte'); 34 | } 35 | 36 | return privateKeyFromId(bytes[0], bytes.slice(1)); 37 | } 38 | -------------------------------------------------------------------------------- /packages/crypto/src/secp256k1/private.ts: -------------------------------------------------------------------------------- 1 | import secp256k1 from 'secp256k1'; 2 | import { RandomFunction, secureRandom } from '../rand'; 3 | import { KindSECP256K1 } from '../keys'; 4 | import { PrivateKey } from '../private'; 5 | import { SECP256K1PublicKey } from './public'; 6 | export const PrivateKeyLen = 32; 7 | 8 | const { privateKeyVerify, publicKeyCreate, ecdsaSign } = secp256k1; 9 | 10 | export class SECP256K1PrivateKey implements PrivateKey { 11 | bytes: Uint8Array; 12 | readonly publicKey: SECP256K1PublicKey; 13 | readonly curve: string = KindSECP256K1; 14 | 15 | constructor(bytes: Uint8Array) { 16 | this.bytes = bytes; 17 | 18 | if (!privateKeyVerify(this.bytes)) { 19 | throw RangeError('bytes are not a i9valid ECDSA private key'); 20 | } 21 | 22 | this.publicKey = new SECP256K1PublicKey(publicKeyCreate(this.bytes)); 23 | } 24 | static generate(rand: RandomFunction = secureRandom): SECP256K1PrivateKey { 25 | return new this(rand(PrivateKeyLen)); 26 | } 27 | async sign(message: Uint8Array): Promise { 28 | const sigObj = ecdsaSign(message, this.bytes); 29 | 30 | const ret = new Uint8Array(65); 31 | ret.set(sigObj.signature, 0); 32 | ret.set(new Uint8Array([sigObj.recid]), 64); 33 | return ret; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/crypto/src/secp256r1/private.ts: -------------------------------------------------------------------------------- 1 | import { secp256r1 } from '@noble/curves/p256'; 2 | import { RandomFunction, secureRandom } from '../rand'; 3 | import { KindSECP256R1 } from '../keys'; 4 | import { PrivateKey } from '../private'; 5 | import { SECP256R1PublicKey } from './public'; 6 | export const SECP256R1PrivateKeyLen = 32; 7 | 8 | export class SECP256R1PrivateKey implements PrivateKey { 9 | readonly publicKey: SECP256R1PublicKey; 10 | readonly curve: string = KindSECP256R1; 11 | 12 | constructor(public readonly bytes: Uint8Array, private readonly rand: RandomFunction = secureRandom) { 13 | if (this.bytes.length !== SECP256R1PrivateKeyLen || !secp256r1.utils.isValidPrivateKey(this.bytes)) { 14 | throw RangeError('bytes are not a valid secp256r1 private key'); 15 | } 16 | this.publicKey = new SECP256R1PublicKey(secp256r1.getPublicKey(this.bytes)); 17 | this.curve = KindSECP256R1; 18 | } 19 | 20 | static generate(rand: RandomFunction = secureRandom): SECP256R1PrivateKey { 21 | return new SECP256R1PrivateKey(rand(), rand); 22 | } 23 | async sign(message: Uint8Array): Promise { 24 | const sig = secp256r1.sign(message, this.bytes, { lowS: true, extraEntropy: this.rand() }); 25 | return sig.toCompactRawBytes(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/internal/src/receiving/mailer/author.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from '../../configuration'; 2 | import { parseMimeText } from '../../formatters/parse'; 3 | import { parseMailerContentFromJSON, SenderVerifier, MailerPayload } from '../../transport'; 4 | 5 | export class MailerAuthorVerifier { 6 | constructor(private readonly senderVerifier: SenderVerifier) {} 7 | 8 | static create(configuration: Configuration) { 9 | return new MailerAuthorVerifier(SenderVerifier.create(configuration)); 10 | } 11 | 12 | /** 13 | * Checks if the author of the mailer is the same as the from address. 14 | * @param payload - The mailer payload 15 | * @returns 16 | */ 17 | async verifyAuthorOwnsFromAddress(payload: MailerPayload, rfcMail: Buffer): Promise { 18 | const mailerContent = parseMailerContentFromJSON(payload.Content.toString()); 19 | const parsedContent = await parseMimeText(rfcMail); 20 | 21 | if (mailerContent.authorMailAddress.address !== parsedContent.mailData.from.address) { 22 | throw new Error('author address does not match from address'); 23 | } 24 | 25 | return await this.senderVerifier.verifySenderOwnsFromAddress( 26 | parsedContent.mailData.from.address, 27 | mailerContent.authorMessagingKey, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/payloadStorage/payloadStorage.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from '../../transport'; 2 | 3 | export interface PayloadStorage { 4 | /** 5 | * Check if the payload can be stored in the context of the storage. 6 | * This check does not guarantee that the payload will be stored. 7 | */ 8 | canStorePayload(payload: Payload): Promise; 9 | 10 | /** 11 | * @param payload The payload to store. 12 | * @returns The resource identifier under which the payload is stored. 13 | */ 14 | storePayload(payload: Payload): Promise; 15 | 16 | /** 17 | * Check if the resource identifier is valid in the context of the storage. 18 | * This check does not guarantee that the payload is retrievable nor that it exists. 19 | * 20 | * @param resourceId The resource identifier under which the payload is stored. 21 | * @returns true if the resource identifier is valid. 22 | */ 23 | canGetPayload(messageId: string, resourceId: string): Promise; 24 | 25 | /** 26 | * @param resourceId The resource identifier under which the payload is stored. 27 | * @returns The payload. 28 | * @throws Error if the payload is not found or cannot be retrieved. 29 | */ 30 | getPayload(messageId: string, resourceId: string): Promise; 31 | } 32 | -------------------------------------------------------------------------------- /packages/signatures/src/mailchain_password_reset.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Signer } from '@mailchain/crypto'; 2 | import { signRawEd25519, verifyRawEd25519 } from './raw_ed25519'; 3 | 4 | export const mailchainPasswordResetMessage = function (username: Uint8Array, expires: Date) { 5 | return Buffer.from( 6 | `\x11Mailchain:\naction: reset-password\nexpires: ${Math.floor( 7 | expires.getTime() / 1000, 8 | )}\nusername: ${username}`, 9 | 'utf-8', 10 | ); 11 | }; 12 | 13 | /** 14 | * Signs a message using the Mailchain username, reset attestation message, and expiration with the identity private key. 15 | */ 16 | export async function signMailchainPasswordReset( 17 | signer: Signer, 18 | username: Uint8Array, 19 | expires: Date, 20 | ): Promise { 21 | return signRawEd25519(signer, mailchainPasswordResetMessage(username, expires)); 22 | } 23 | 24 | /** 25 | * Verifies message linking a username, reset attestation message, and expiration with an identity key is valid. 26 | */ 27 | export async function verifyMailchainPasswordReset( 28 | key: PublicKey, 29 | signature: Uint8Array, 30 | username: Uint8Array, 31 | expires: Date, 32 | ): Promise { 33 | return verifyRawEd25519(key, mailchainPasswordResetMessage(username, expires), signature); 34 | } 35 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/tezos/test.const.ts: -------------------------------------------------------------------------------- 1 | // AliceED25519PublicKey 2 | export const AliceED25519TzPublicKey = 'edpkuWXy3DeVKmrwvSa9iYRHuCbkL8YeLiXWD2EHnCfVBF3E6B3CP9'; 3 | export const AliceTz1AddressStr = 'tz1cxdX7rUDr4G1LcHH2kVNLzEXBo7va15eV'; 4 | // BobED25519PublicKey 5 | export const BobED25519TzPublicKey = 'edpktzZxD34kwnRNFFAnYTsPJp17ycS4RQbZ1Y7yyXrUyB3rgPhVUL'; 6 | export const BobTz1AddressStr = 'tz1NCxL77QLtoPHjdCx75v7LcjfBtTgZAPv5'; 7 | 8 | // AliceSECP256K1PublicKey 9 | export const AliceSECP256K1TzPublicKey = 'sppk7a7FbTvhn3Qxh5WpXVdNK88xsdpWAZF2ux2RNmvikKFtXYdHdMy'; 10 | export const AliceTz2AddressStr = 'tz2ToZF5fjmcawoUQUa6NoV3jsRmmZNwuEyZ'; 11 | // BobSECP256K1PublicKey 12 | export const BobSECP256K1TzPublicKey = 'sppk7ch3Rj9KwqQHFWoUX93ZVaqxEDJ9z5X4rhd4gDTb4e36cqUY4PW'; 13 | export const BobTz2AddressStr = 'tz29g81yQuHQdXrwtHwbfW6UnNHwfXyfetzZ'; 14 | 15 | // AliceSECP256R1PublicKey 16 | export const AliceSECP256R1TzPublicKey = 'p2pk66tTYL5EvahKAXncbtbRPBkAnxo3CszzUho5wPCgWauBMyvybuB'; 17 | export const AliceTz3AddressStr = 'tz3Lfm6CyfSTZ7EgMckptZZGiPxzs9GK59At'; 18 | // BobSECP256R1PublicKey 19 | export const BobSECP256R1TzPublicKey = 'p2pk66s1R6D2K6dQUPvLtzaQRitR8T8GCinUbsVZrwD3vmU5L3xE3PE'; 20 | export const BobTz3AddressStr = 'tz3fUhqnq67nXVL8wnk2G5xttXMJYF6qrEE5'; 21 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/publicKeyEncrypter.ts: -------------------------------------------------------------------------------- 1 | import { KeyExchange } from '../keyExchange'; 2 | import { EncryptedContent, Encrypter } from '../cipher'; 3 | import { RandomFunction, secureRandom } from '../../rand'; 4 | import { PublicKey } from '../../public'; 5 | import { fromPublicKey } from '../ecdh/ecdh'; 6 | import { easySeal } from './secretbox'; 7 | import { serializePublicKeyEncryptedContent } from './serialization'; 8 | 9 | export class PublicKeyEncrypter implements Encrypter { 10 | private _keyEx: KeyExchange; 11 | private _pubKey: PublicKey; 12 | private _rand: RandomFunction; 13 | 14 | constructor(keyEx: KeyExchange, pubKey: PublicKey, rand: RandomFunction = secureRandom) { 15 | this._rand = rand; 16 | this._keyEx = keyEx; 17 | this._pubKey = pubKey; 18 | } 19 | static FromPublicKey(key: PublicKey): PublicKeyEncrypter { 20 | return new this(fromPublicKey(key), key); 21 | } 22 | 23 | async encrypt(input: Uint8Array): Promise { 24 | const ephemeralPrvKey = await this._keyEx.EphemeralKey(); 25 | const sharedSecret = await this._keyEx.SharedSecret(ephemeralPrvKey, this._pubKey); 26 | const sealedBox = easySeal(input, sharedSecret, this._rand); 27 | 28 | return serializePublicKeyEncryptedContent(sealedBox, ephemeralPrvKey.publicKey); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/presentation.ts: -------------------------------------------------------------------------------- 1 | import { PresentationPayload, VerifiableCredential } from 'did-jwt-vc'; 2 | import { DecentralizedIdentifier, MailchainDecentralizedIdentifier } from './did'; 3 | import { W3C_CREDENTIALS_CONTEXT } from './context'; 4 | 5 | type CreatePresentationPayloadParams = { 6 | requestId: string; 7 | verifiableCredential: VerifiableCredential; 8 | verifier: DecentralizedIdentifier; 9 | issuanceDate: Date; 10 | /** 11 | * The date and time that the credential will expire. 12 | */ 13 | expirationDate?: Date; 14 | holder: MailchainDecentralizedIdentifier; 15 | }; 16 | 17 | /** 18 | * Create presentation payload that is then signed to create a verified presentation. 19 | * @param params 20 | * @returns 21 | */ 22 | export function createPresentationPayload(params: CreatePresentationPayloadParams): PresentationPayload { 23 | const { verifiableCredential, verifier, issuanceDate, expirationDate, holder, requestId } = params; 24 | 25 | return { 26 | '@context': [W3C_CREDENTIALS_CONTEXT], 27 | type: ['VerifiablePresentation'], 28 | requestId, 29 | holder, 30 | verifiableCredential: [verifiableCredential], 31 | verifier, 32 | issuanceDate: issuanceDate.toUTCString(), 33 | expirationDate: expirationDate ? expirationDate.toUTCString() : undefined, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /packages/signatures/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class ProtocolIsEmptyError extends Error { 2 | constructor() { 3 | super('protocol is empty'); 4 | } 5 | } 6 | export class AddressIsEmptyError extends Error { 7 | constructor() { 8 | super('address is empty'); 9 | } 10 | } 11 | 12 | export class MessagingKeyVerificationError extends Error { 13 | readonly type = 'messaging_key_validation_failed'; 14 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#messaging_key_validation_failed'; 15 | constructor() { 16 | super('Messaging key validation failed and is not useable for this address.'); 17 | } 18 | } 19 | 20 | export class ProvidedMessagingKeyIncorrectError extends Error { 21 | readonly type = 'provided_messaging_key_incorrect'; 22 | readonly docs = 'https://docs.mailchain.com/developer/errors/codes#provided_messaging_key_incorrect'; 23 | constructor(type: 'sender' | 'signer' | 'address') { 24 | super(`Provided messaging key does not match. Use the latest messaging key owned by ${type}.`); 25 | } 26 | } 27 | 28 | export class AddressMustBeProtocolAddressError extends Error { 29 | constructor() { 30 | super('address must be a protocol address'); 31 | } 32 | } 33 | 34 | export class PublicKeyNotFoundError extends Error { 35 | constructor() { 36 | super('Mailchain public key not found'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/publicKeyEndToEnd.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliceED25519PublicKey, 3 | BobED25519PublicKey, 4 | BobED25519PrivateKey, 5 | AliceED25519PrivateKey, 6 | } from '../../ed25519/test.const'; 7 | import { ED25519KeyExchange } from '../ecdh/ed25519'; 8 | import { PublicKeyDecrypter, PublicKeyEncrypter } from '.'; 9 | 10 | describe('encrypt-then-decrypt', () => { 11 | const tests = [ 12 | { 13 | name: 'ed25519-to-bob', 14 | keyEx: new ED25519KeyExchange(), 15 | recipientPublicKey: BobED25519PublicKey, 16 | recipientPrivateKey: BobED25519PrivateKey, 17 | message: new Uint8Array(Buffer.from('message', 'ascii')), 18 | }, 19 | { 20 | name: 'ed25519-to-alice', 21 | keyEx: new ED25519KeyExchange(), 22 | recipientPublicKey: AliceED25519PublicKey, 23 | recipientPrivateKey: AliceED25519PrivateKey, 24 | message: new Uint8Array(Buffer.from('message', 'ascii')), 25 | }, 26 | ]; 27 | test.each(tests)('%p', async (test) => { 28 | const encrypter = new PublicKeyEncrypter(test.keyEx, test.recipientPublicKey); 29 | const encrypted = await encrypter.encrypt(test.message); 30 | 31 | const decrypter = new PublicKeyDecrypter(test.keyEx, test.recipientPrivateKey); 32 | const decrypted = await decrypter.decrypt(encrypted); 33 | 34 | expect(decrypted).toEqual(test.message); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/internal/src/formatters/simpleMimeHeaderParser.ts: -------------------------------------------------------------------------------- 1 | export function simpleMimeHeaderParser(message: string): Map { 2 | const result = new Map(); 3 | const headersBlankLine = message.indexOf('\r\n\r\n'); 4 | const headersPart = message.substring(0, headersBlankLine); 5 | const lines = headersPart.split('\r\n'); 6 | 7 | let rawHeader = ''; 8 | for (const line of lines) { 9 | if (/^\s/.test(line)) { 10 | // starts with white-space, continuing from previous line 11 | rawHeader += ' ' + line.trimStart(); 12 | } else { 13 | if (rawHeader.length > 0) { 14 | const [key, value] = processRawHeader(rawHeader); 15 | result.set(key, value); 16 | } 17 | rawHeader = line; 18 | } 19 | } 20 | // Last line is not handled by the for loop 21 | if (rawHeader.length > 0) { 22 | const [key, value] = processRawHeader(rawHeader); 23 | result.set(key, value); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | function processRawHeader(rawHeader: string): [string, string] { 30 | const colonIndex = rawHeader.indexOf(':'); 31 | if (colonIndex > 0) { 32 | const key = rawHeader.substring(0, colonIndex).trim(); 33 | const value = rawHeader.substring(colonIndex + 1, rawHeader.length).trim(); 34 | return [key, value]; 35 | } 36 | throw new Error(`invalid header formatting [${rawHeader}]`); 37 | } 38 | -------------------------------------------------------------------------------- /packages/signatures/src/raw_ed25519.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PrivateKey, AliceED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { signRawEd25519, verifyRawEd25519 } from './raw_ed25519'; 3 | 4 | describe('Raw EE25519 sign/verify', () => { 5 | it('should sign with ED25519', async () => { 6 | const message = Buffer.from('msg', 'utf-8'); 7 | const expectedSignature = Buffer.from('signed', 'utf-8'); 8 | const mockSign = jest.spyOn(AliceED25519PrivateKey, 'sign'); 9 | mockSign.mockReturnValue(Promise.resolve(expectedSignature)); 10 | 11 | const actualSignature = await signRawEd25519(AliceED25519PrivateKey, message); 12 | 13 | expect(actualSignature).toEqual(expectedSignature); 14 | expect(mockSign.mock.calls[0][0]).toEqual(message); 15 | }); 16 | 17 | it('should verify with ED25519', async () => { 18 | const message = Buffer.from('msg', 'utf-8'); 19 | const signature = Buffer.from('signed', 'utf-8'); 20 | const mockVerify = jest.spyOn(AliceED25519PublicKey, 'verify'); 21 | mockVerify.mockReturnValue(Promise.resolve(true)); 22 | 23 | const valid = await verifyRawEd25519(AliceED25519PublicKey, message, signature); 24 | 25 | expect(valid).toEqual(true); 26 | expect(mockVerify.mock.calls[0]).toEqual([message, signature]); 27 | }); 28 | 29 | afterEach(() => { 30 | jest.resetAllMocks(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/encoding/src/base58Check.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha256'; 2 | import { decodeBase58, encodeBase58 } from './base58'; 3 | 4 | export function decodeBase58Check(input: string): Uint8Array { 5 | const validatedPayload = validatePayload(decodeBase58(input)); 6 | if (validatedPayload == null) throw new Error('failed input checksum validation'); 7 | return validatedPayload; 8 | } 9 | 10 | export function encodeBase58Check(input: Uint8Array): string { 11 | const checksum = calcChecksum(input).slice(0, 4); 12 | return encodeBase58(Uint8Array.from([...input, ...checksum])); 13 | } 14 | 15 | // Ref: https://github.com/bitcoinjs/bs58check 16 | function calcChecksum(payload: Uint8Array): Uint8Array { 17 | return sha256(sha256(payload)); 18 | } 19 | 20 | // Ref: https://github.com/bitcoinjs/bs58check 21 | function validatePayload(payloadWithChecksum: Uint8Array): Uint8Array | null { 22 | const payload = payloadWithChecksum.slice(0, -4); 23 | const payloadChecksum = payloadWithChecksum.slice(-4); 24 | const checksum = calcChecksum(payload); 25 | 26 | // Compare the checksum bytes with XOR operator 27 | if ( 28 | (payloadChecksum[0] ^ checksum[0]) | 29 | (payloadChecksum[1] ^ checksum[1]) | 30 | (payloadChecksum[2] ^ checksum[2]) | 31 | (payloadChecksum[3] ^ checksum[3]) 32 | ) { 33 | return null; 34 | } 35 | 36 | return payload; 37 | } 38 | -------------------------------------------------------------------------------- /packages/internal/src/formatters/__snapshots__/parse.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`parse should handle legacy message content and subject 1`] = ` 4 | { 5 | "blindCarbonCopyRecipients": [ 6 | { 7 | "address": "rec5@mailchain.local", 8 | "name": "rec5", 9 | }, 10 | { 11 | "address": "rec6@mailchain.local", 12 | "name": "rec6", 13 | }, 14 | ], 15 | "carbonCopyRecipients": [ 16 | { 17 | "address": "rec3@mailchain.local", 18 | "name": "rec3", 19 | }, 20 | { 21 | "address": "rec4@mailchain.local", 22 | "name": "rec4", 23 | }, 24 | ], 25 | "date": 2022-06-06T00:00:00.000Z, 26 | "from": { 27 | "address": "1337@mailchain.com", 28 | "name": "1337", 29 | }, 30 | "id": "123@mailchain.local", 31 | "message": "

Lorem ipsum dolor sit amet

Лорем ипсум долор сит амет

Λορεμ ιπσθμ δολορ σιτ αμετ

側経意責家方家閉討店暖育田庁載社転線宇

🐺🏢🔹🔯🍵 🕒

", 32 | "plainTextMessage": undefined, 33 | "recipients": [ 34 | { 35 | "address": "rec1@mailchain.local", 36 | "name": "rec1", 37 | }, 38 | { 39 | "address": "rec2@mailchain.local", 40 | "name": "rec2", 41 | }, 42 | ], 43 | "replyTo": undefined, 44 | "subject": "Lorem ipsum dolor sit ametЛорем ипсум долор сит аметΛορεμ ιπσθμ δολορ σιτ αμετ側経意責家方家閉討店暖育田庁載社転線宇🐺🏢🔹🔯🍵 🕒", 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /packages/internal/src/user/types.ts: -------------------------------------------------------------------------------- 1 | import { MailchainAddress, ProtocolType } from '@mailchain/addressing'; 2 | import { PublicKey } from '@mailchain/crypto'; 3 | 4 | /** 5 | * Represents different type of alias to be set for the {@link UserMailbox}. 6 | */ 7 | export type Alias = { 8 | address: MailchainAddress; 9 | allowSending: boolean; 10 | allowReceiving: boolean; 11 | }; 12 | 13 | /** 14 | * Entity for sending and receiving messages based on the provided identity key and messaging key. 15 | */ 16 | export type UserMailbox = { 17 | id: string; 18 | /** 19 | * - `'account'` - Mailchain account (example: `'alice@mailchain.com'`). 20 | * - `'wallet'` - registered mailbox based blockchain wallet identity key (example: `'0xEBaae0532dF65ee3f1623f324C9620bB84c8af8d@ethereum.mailchain.com'`) 21 | */ 22 | type: 'account' | 'wallet'; 23 | identityKey: PublicKey; 24 | /** The user preferred label to shown for this mailbox. If `null`, no user preferred label is defined, the application is free to compute one at runtime. */ 25 | label: string | null; 26 | /** Will contain at least one {@link Alias}. The first entry in the array is considered as 'default' alias. */ 27 | aliases: [Alias, ...Alias[]]; 28 | /** Ingredients for the creation of private messaging key via the keyring. */ 29 | messagingKeyParams: { 30 | address: Uint8Array; 31 | protocol: ProtocolType; 32 | network: string; 33 | nonce: number; 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/internal/src/mailchainResult.ts: -------------------------------------------------------------------------------- 1 | export type SuccessMailchainResult = { 2 | data: D; 3 | error?: undefined; 4 | }; 5 | export type ErrorMailchainResult = { 6 | error: E; 7 | data?: undefined; 8 | }; 9 | 10 | export type MailchainResult = SuccessMailchainResult | ErrorMailchainResult; 11 | 12 | export type ResultsWithParams = { 13 | result: MailchainResult; 14 | params: P; 15 | }; 16 | 17 | export function partitionMailchainResults( 18 | calls: ResultsWithParams[], 19 | ): { 20 | successes: Array<{ params: P; data: T }>; 21 | failures: Array<{ params: P; error: E }>; 22 | } { 23 | const successes: Array<{ params: P; data: T }> = []; 24 | const failures: Array<{ params: P; error: E }> = []; 25 | 26 | for (const call of calls) { 27 | const { result, params } = call; 28 | if (isErrorMailchainResult(result)) failures.push({ params, error: result.error }); 29 | if (isSuccessMailchainResult(result)) successes.push({ params, data: result.data }); 30 | } 31 | 32 | return { successes, failures }; 33 | } 34 | 35 | function isErrorMailchainResult(r: MailchainResult): r is ErrorMailchainResult { 36 | return r.error != null; 37 | } 38 | 39 | function isSuccessMailchainResult(r: MailchainResult): r is SuccessMailchainResult { 40 | return r.data != null; 41 | } 42 | -------------------------------------------------------------------------------- /packages/addressing/src/walletAddress.test.ts: -------------------------------------------------------------------------------- 1 | import { ETHEREUM, MAILCHAIN, ProtocolType } from './protocols'; 2 | import { createWalletAddress, MailchainAddress } from '.'; 3 | 4 | const createWalletAddressTests: { 5 | testName: string; 6 | params: Parameters; 7 | expected: MailchainAddress; 8 | }[] = [ 9 | { 10 | testName: 'simple mailchain', 11 | params: ['alice', MAILCHAIN, 'mailchain.test'], 12 | expected: { username: 'alice', domain: 'mailchain.test' }, 13 | }, 14 | { 15 | testName: 'value mixed case', 16 | params: ['AlIcE', MAILCHAIN, 'mailchain.test'], 17 | expected: { username: 'alice', domain: 'mailchain.test' }, 18 | }, 19 | { 20 | testName: 'value and protocol mixed case', 21 | params: ['AlIcE', 'MaIlChAiN' as ProtocolType, 'mailchain.test'], 22 | expected: { username: 'alice', domain: 'mailchain.test' }, 23 | }, 24 | { 25 | testName: 'domain mixed case', 26 | params: ['alice', MAILCHAIN, 'MaIlChAiN.TeSt'], 27 | expected: { username: 'alice', domain: 'mailchain.test' }, 28 | }, 29 | { 30 | testName: 'ethereum address', 31 | params: ['0xdDfFC3003797e44FCd103eE7A4aE78Ed02853A55', ETHEREUM, 'mailchain.test'], 32 | expected: { username: '0xddffc3003797e44fcd103ee7a4ae78ed02853a55', domain: 'ethereum.mailchain.test' }, 33 | }, 34 | ]; 35 | 36 | test.each(createWalletAddressTests)('$testName', ({ params, expected }) => { 37 | expect(createWalletAddress(...params)).toEqual(expected); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/payload.ts: -------------------------------------------------------------------------------- 1 | import { VerifiableCredential } from 'did-jwt-vc'; 2 | import { Proof } from 'did-jwt-vc/lib/types'; 3 | import { DecentralizedIdentifier } from './did'; 4 | import { TermsOfUse } from './termsOfUse'; 5 | import { CredentialSubject } from './subject'; 6 | import { W3C_CREDENTIALS_CONTEXT } from './context'; 7 | 8 | const MailchainMessagingKeyCredential = 'MailchainMessagingKeyCredential'; 9 | export const CredentialPayloadTypes = [MailchainMessagingKeyCredential] as const; 10 | export type CredentialPayloadType = (typeof CredentialPayloadTypes)[number]; 11 | 12 | type CreateVerifiableCredentialParams = { 13 | type: CredentialPayloadType; 14 | credentialSubjects: CredentialSubject[]; 15 | issuanceDate: Date; 16 | issuerId: DecentralizedIdentifier; 17 | termsOfUse: TermsOfUse[]; 18 | proof: Proof; 19 | }; 20 | 21 | /** 22 | * Creates a verifiable credential using a provided proof instead of signature. 23 | */ 24 | export function createVerifiableCredential(params: CreateVerifiableCredentialParams): VerifiableCredential { 25 | const { type, credentialSubjects, issuanceDate, issuerId, proof, termsOfUse } = params; 26 | 27 | return { 28 | '@context': [W3C_CREDENTIALS_CONTEXT], 29 | credentialSubject: credentialSubjects, 30 | type: ['VerifiableCredential', type], 31 | issuanceDate: issuanceDate.toISOString(), 32 | issuer: { 33 | id: issuerId, 34 | }, 35 | termsOfUse, 36 | proof, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/resolver.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceSECP256K1PublicAddressStr } from '@mailchain/addressing/protocols/ethereum/test.const'; 2 | import { mock } from 'jest-mock-extended'; 3 | import { AliceED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 4 | import { AliceSECP256K1PublicKey } from '@mailchain/crypto/secp256k1/test.const'; 5 | import { MessagingKeys, Proof } from '../messagingKeys'; 6 | import { MailchainDIDMessagingKeyResolver } from './resolver'; 7 | 8 | describe('MailchainDIDMessagingKeyResolver', () => { 9 | it('should resolve', async () => { 10 | const mockMessagingKeys = mock(); 11 | mockMessagingKeys.resolveIndividual.mockResolvedValue({ 12 | data: { 13 | mailchainAddress: `${AliceSECP256K1PublicAddressStr}@ethereum.mailchain.com`, 14 | messagingKey: AliceED25519PublicKey, 15 | protocol: 'ethereum', 16 | protocolAddress: AliceSECP256K1PublicAddressStr, 17 | type: 'registered', 18 | identityKey: AliceSECP256K1PublicKey, 19 | proof: {} as Proof, 20 | }, 21 | }); 22 | 23 | const target = new MailchainDIDMessagingKeyResolver(mockMessagingKeys); 24 | const result = await target.resolve(`did:mailchain:${AliceSECP256K1PublicAddressStr}@ethereum.mailchain.com`); 25 | expect(result).toMatchSnapshot('result'); 26 | expect(mockMessagingKeys.resolveIndividual).toHaveBeenCalledWith( 27 | `${AliceSECP256K1PublicAddressStr}@ethereum.mailchain.com`, 28 | ); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/internal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailchain/internal", 3 | "version": "0.31.0", 4 | "description": "Mailchain internal library", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Mailchain", 8 | "email": "support@mailchain.co", 9 | "url": "https://mailchain.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mailchain/mailchain-sdk-js.git", 14 | "directory": "internal" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mailchain/mailchain-sdk-js/issues" 18 | }, 19 | "dependencies": { 20 | "@mailchain/addressing": "0.31.0", 21 | "@mailchain/api": "0.31.0", 22 | "@mailchain/crypto": "0.31.0", 23 | "@mailchain/encoding": "0.31.0", 24 | "@mailchain/keyring": "0.31.0", 25 | "@mailchain/message-composer": "0.31.0", 26 | "@mailchain/signatures": "0.31.0", 27 | "@noble/hashes": "^1.3.0", 28 | "axios": "1.6.0", 29 | "canonicalize": "^1.0.8", 30 | "did-jwt-vc": "3.1.0", 31 | "did-resolver": "^4.1.0", 32 | "email-addresses": "5.0.0", 33 | "emailjs-mime-parser": "^2.0.7", 34 | "lodash": "^4.17.21", 35 | "long": "^5.2.1", 36 | "protobufjs": "^7.2.5", 37 | "striptags": "^3.2.0" 38 | }, 39 | "devDependencies": { 40 | "jest": "^29.7.0", 41 | "jest-mock-extended": "^3.0.5", 42 | "typescript": "^5.1.3" 43 | }, 44 | "scripts": { 45 | "build": "run -T tsup", 46 | "test": "run -T jest", 47 | "lint:test": "yarn run -T eslint --ext js,ts,tsx src", 48 | "lint:fix": "yarn run -T eslint --fix --ext js,ts,tsx src" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/privateKeyDecrypter.ts: -------------------------------------------------------------------------------- 1 | import ed2curve from 'ed2curve'; 2 | import { idFromPrivateKey } from '../../multikey/ids'; 3 | import { EncryptedContent, PlainContent, Decrypter } from '../cipher'; 4 | import { PrivateKey } from '../../private'; 5 | import { SECP256K1PrivateKey } from '../../secp256k1/private'; 6 | import { ED25519PrivateKey } from '../../ed25519/private'; 7 | import { deserializePrivateKeyEncryptedContent } from './serialization'; 8 | import { easyOpen } from './secretbox'; 9 | 10 | export class PrivateKeyDecrypter implements Decrypter { 11 | private _secretKey: Uint8Array; 12 | private _keyId: number; 13 | 14 | constructor(privateKey: PrivateKey) { 15 | this._keyId = idFromPrivateKey(privateKey); 16 | 17 | switch (privateKey.constructor) { 18 | case ED25519PrivateKey: 19 | this._secretKey = ed2curve.convertSecretKey(privateKey.bytes); 20 | break; 21 | case SECP256K1PrivateKey: 22 | this._secretKey = privateKey.bytes; 23 | break; 24 | default: 25 | throw RangeError('unknown private key type'); 26 | } 27 | } 28 | static fromPrivateKey(key: PrivateKey): PrivateKeyDecrypter { 29 | return new this(key); 30 | } 31 | 32 | async decrypt(input: EncryptedContent): Promise { 33 | const secretData = deserializePrivateKeyEncryptedContent(input); 34 | if (this._keyId !== secretData.keyId) { 35 | throw Error('key id does not match supplied key'); 36 | } 37 | 38 | return easyOpen(secretData.encryptedContent, this._secretKey); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/encoding/src/utf8.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeUtf8, encodeUtf8 } from './utf8'; 2 | describe('Buffer', () => { 3 | it('Encode and decode are the same', () => { 4 | const arr = new Uint8Array(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 127, 125, 0, 1])); 5 | expect(decodeUtf8(encodeUtf8(arr))).toEqual(arr); 6 | }); 7 | it('Encode and decode should not be the same because of utf8 sequnce is not valid', () => { 8 | const arr = new Uint8Array(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 140, 125, 0, 1])); 9 | expect(decodeUtf8(encodeUtf8(arr))).not.toEqual(arr); 10 | }); 11 | }); 12 | 13 | describe('Decode', () => { 14 | const tests = [ 15 | { 16 | input: "'😲🥳😲🥳🙂1323'", 17 | expected: Uint8Array.from([ 18 | 0x27, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 19 | 0xf0, 0x9f, 0x99, 0x82, 0x31, 0x33, 0x32, 0x33, 0x27, 20 | ]), 21 | }, 22 | ]; 23 | 24 | test.each(tests)('$name', async (test) => { 25 | expect(decodeUtf8(test.input)).toEqual(test.expected); 26 | }); 27 | }); 28 | 29 | describe('Encode', () => { 30 | const tests = [ 31 | { 32 | input: Uint8Array.from([ 33 | 0x27, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 34 | 0xf0, 0x9f, 0x99, 0x82, 0x31, 0x33, 0x32, 0x33, 0x27, 35 | ]), 36 | expected: "'😲🥳😲🥳🙂1323'", 37 | }, 38 | ]; 39 | 40 | test.each(tests)('$name', async (test) => { 41 | expect(encodeUtf8(test.input)).toEqual(test.expected); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/internal/src/migration.test.ts: -------------------------------------------------------------------------------- 1 | import { combineMigrations, nopMigration, MigrationRule } from './migration'; 2 | 3 | describe('migration', () => { 4 | it('should return false for should apply when none of the migration rules require it', async () => { 5 | const shouldApply = await combineMigrations(nopMigration(), nopMigration(), nopMigration()).shouldApply(0); 6 | 7 | expect(shouldApply).toBe(false); 8 | }); 9 | 10 | it('should return true for shouldApply when even single migration rule requires it', async () => { 11 | const shouldApply = await combineMigrations( 12 | nopMigration(), 13 | { ...nopMigration(), shouldApply: () => Promise.resolve(true) }, 14 | nopMigration(), 15 | ).shouldApply(0); 16 | 17 | expect(shouldApply).toBe(true); 18 | }); 19 | 20 | it('should apply migration only of the rules that require it', async () => { 21 | const migration = combineMigrations( 22 | nopMigration(), 23 | incrementNumberMigration(false), 24 | incrementNumberMigration(true), 25 | incrementNumberMigration(true), 26 | incrementNumberMigration(false), 27 | ); 28 | 29 | const shouldApply = await migration.shouldApply(0); 30 | const migrated = await migration.apply(0); 31 | 32 | expect(shouldApply).toEqual(true); 33 | expect(migrated).toEqual(2); 34 | }); 35 | }); 36 | 37 | function incrementNumberMigration(shouldApply: boolean): MigrationRule { 38 | return { 39 | shouldApply: () => Promise.resolve(shouldApply), 40 | apply: (data) => Promise.resolve(data + 1), 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/signatures/src/publickey/ethereum.ts: -------------------------------------------------------------------------------- 1 | import { SECP256K1PublicKey } from '@mailchain/crypto'; 2 | import { encodeHex } from '@mailchain/encoding'; 3 | import isEqual from 'lodash/isEqual.js'; 4 | import { ETHEREUM, addressFromPublicKey } from '@mailchain/addressing'; 5 | import { getMessageHash } from '../eth_personal'; 6 | 7 | /** 8 | * Gets the public key from signature and compares it to the expected address to validate it's correct. 9 | * 10 | * The public key that {@link SECP256K1PublicKey.fromSignature} returns will verify the message with the signature. 11 | * However it's not guaranteed that the returned public key will be for the expected address. 12 | * @param message the message being signed 13 | * @param signature the signature of the message made with the private key part of the public key we are trying to extract 14 | */ 15 | export async function ethereumPublicKeyFromSignature( 16 | message: Uint8Array, 17 | signature: Uint8Array, 18 | expectedAddress: Uint8Array, 19 | ): Promise { 20 | const messageHash = await getMessageHash(message); 21 | const publicKey = await SECP256K1PublicKey.fromSignature(messageHash, signature); 22 | 23 | const address = await addressFromPublicKey(publicKey, ETHEREUM); 24 | 25 | if (!isEqual(address, expectedAddress)) { 26 | throw new Error( 27 | `inconsistent public key calculated, expected address "${encodeHex( 28 | expectedAddress, 29 | )}" but actual is "${encodeHex(address)} (both hex encoded)"`, 30 | ); 31 | } 32 | 33 | return publicKey; 34 | } 35 | -------------------------------------------------------------------------------- /packages/internal/src/mailboxRuleEngine/actions.ts: -------------------------------------------------------------------------------- 1 | import { SystemMessageLabel } from '../mailbox/types'; 2 | import { MailboxRuleAction } from './rule'; 3 | 4 | /** 5 | * Action that should add a system label to a message. 6 | */ 7 | export type ActionAddSystemLabel = MailboxRuleAction & { 8 | type: 'AddSystemLabel'; 9 | }; 10 | 11 | /** 12 | * Type guard for `ActionAddSystemLabel` 13 | */ 14 | export function isActionAddSystemLabel(action: MailboxRuleAction): action is ActionAddSystemLabel { 15 | return action.type === 'AddSystemLabel'; 16 | } 17 | 18 | /** 19 | * Create a `ActionAddSystemLabel` action. 20 | */ 21 | export function actionAddSystemLabel(label: ActionAddSystemLabel['value']): ActionAddSystemLabel { 22 | return { 23 | type: 'AddSystemLabel', 24 | value: label, 25 | }; 26 | } 27 | 28 | /** 29 | * Action that should remove a system label from a message. 30 | */ 31 | export type ActionRemoveSystemLabel = MailboxRuleAction & { 32 | type: 'RemoveSystemLabel'; 33 | }; 34 | 35 | /** 36 | * Type guard for `ActionRemoveSystemLabel` 37 | */ 38 | export function isActionRemoveSystemLabel(action: MailboxRuleAction): action is ActionRemoveSystemLabel { 39 | return action.type === 'RemoveSystemLabel'; 40 | } 41 | 42 | /** 43 | * Create a `ActionRemoveSystemLabel` action. 44 | */ 45 | export function actionRemoveSystemLabel(label: ActionAddSystemLabel['value']): ActionRemoveSystemLabel { 46 | return { 47 | type: 'RemoveSystemLabel', 48 | value: label, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/types.ts: -------------------------------------------------------------------------------- 1 | import type { PublicKey } from '@mailchain/crypto'; 2 | import type { PayloadHeaders } from '../transport/payload/headers'; 3 | 4 | export type MessagesOverview = { 5 | total: number; 6 | unread: number; 7 | folders: Map; 8 | }; 9 | 10 | export type FolderMessagesOverview = { 11 | total: number; 12 | unread: number; 13 | mailboxes: Map; 14 | }; 15 | 16 | export type MailboxMessagesOverview = { 17 | total: number; 18 | unread: number; 19 | }; 20 | 21 | export type MessageKind = 'mail' | 'vc-request'; 22 | 23 | export type MessagePreview = { 24 | kind: MessageKind; 25 | mailbox: PublicKey; 26 | messageId: string; 27 | messageBodyResourceId: string; 28 | from: string; 29 | owner: string; 30 | subject: string; 31 | snippet: string; 32 | isRead: boolean; 33 | systemLabels: SystemMessageLabel[]; 34 | hasAttachment: boolean; 35 | timestamp: Date; 36 | to: string[]; 37 | cc: string[]; 38 | bcc: string[]; 39 | }; 40 | 41 | export type Message = { 42 | replyTo?: string; 43 | body: string; 44 | payloadHeaders: H; 45 | }; 46 | 47 | /** Copy from services/inbox/internal/datastore/labels.go */ 48 | export const SYSTEM_MESSAGE_LABELS = [ 49 | 'archive', 50 | 'draft', 51 | 'inbox', 52 | 'sent', 53 | 'spam', 54 | 'starred', 55 | 'trash', 56 | 'unread', 57 | 'outbox', 58 | ] as const; 59 | export type SystemMessageLabel = (typeof SYSTEM_MESSAGE_LABELS)[number]; 60 | export type UserMessageLabel = string; 61 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/userMailboxHasher.test.ts: -------------------------------------------------------------------------------- 1 | import { encodeBase64 } from '@mailchain/encoding'; 2 | import { aliceKeyRing, bobKeyRing } from '@mailchain/keyring/test.const'; 3 | import { AliceAccountMailbox, AliceWalletMailbox } from '../user/test.const'; 4 | import { createMailchainUserMailboxHasher } from './userMailboxHasher'; 5 | 6 | describe('userMailboxHasher', () => { 7 | it('should create different hash for different mailboxes', async () => { 8 | const hasher = createMailchainUserMailboxHasher(aliceKeyRing); 9 | 10 | const accountMailboxHash = await hasher(AliceAccountMailbox); 11 | const walletMailboxHash = await hasher(AliceWalletMailbox); 12 | 13 | expect(accountMailboxHash).not.toEqual(walletMailboxHash); 14 | expect(encodeBase64(accountMailboxHash)).toEqual('tMqSrUrtyZFQvNMNiC0N+5lA2YO251BGO8rOn14SZcU='); 15 | expect(encodeBase64(walletMailboxHash)).toEqual('V4fWUJ9BMklgfZ5/eIho0ByCp4KoxS+2965MYN2JASY='); 16 | }); 17 | 18 | it('should create different hash for same mailbox but different keyring', async () => { 19 | const aliceHasher = createMailchainUserMailboxHasher(aliceKeyRing); 20 | const bobHasher = createMailchainUserMailboxHasher(bobKeyRing); 21 | 22 | const hashByAlice = await aliceHasher(AliceWalletMailbox); 23 | const hashByBob = await bobHasher(AliceWalletMailbox); 24 | 25 | expect(hashByAlice).not.toEqual(hashByBob); 26 | expect(encodeBase64(hashByAlice)).toEqual('V4fWUJ9BMklgfZ5/eIho0ByCp4KoxS+2965MYN2JASY='); 27 | expect(encodeBase64(hashByBob)).toEqual('Hf0Yilygl9dqW93Ls9VlxiuQJoKwRDzIL0PYMFWK4wM='); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/keys.test.ts: -------------------------------------------------------------------------------- 1 | import { AliceED25519PrivateKey, AliceED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 2 | import { AliceSECP256K1PrivateKey, AliceSECP256K1PublicKey } from '@mailchain/crypto/secp256k1/test.const'; 3 | import { AliceSECP256R1PublicKey } from '@mailchain/crypto/secp256r1/test.const'; 4 | import { 5 | privateMessagingKeyFromHex, 6 | privateMessagingKeyToHex, 7 | publicMessagingKeyFromHex, 8 | publicMessagingKeyToHex, 9 | } from './keys'; 10 | 11 | describe('publicMessagingKey()', () => { 12 | const tests = [ 13 | { 14 | name: 'ed25519 alice', 15 | arg: AliceED25519PublicKey, 16 | }, 17 | { 18 | name: 'secp256k1 alice', 19 | arg: AliceSECP256K1PublicKey, 20 | }, 21 | { 22 | name: 'secp256r1 alice', 23 | arg: AliceSECP256R1PublicKey, 24 | }, 25 | ]; 26 | test.each(tests)('$name', async (test) => { 27 | const hex = publicMessagingKeyToHex(test.arg); 28 | expect(publicMessagingKeyFromHex(hex)).toEqual(test.arg); 29 | }); 30 | }); 31 | 32 | describe('privateMessagingKey()', () => { 33 | const tests = [ 34 | { 35 | name: 'ed25519 alice', 36 | arg: AliceED25519PrivateKey, 37 | }, 38 | { 39 | name: 'secp256k1 alice', 40 | arg: AliceSECP256K1PrivateKey, 41 | }, 42 | // { 43 | // name: 'secp256r1 alice', 44 | // arg: AliceSECP256R1PrivateKey, 45 | // }, not tested due to rand function 46 | ]; 47 | test.each(tests)('$name', async (test) => { 48 | const hex = privateMessagingKeyToHex(test.arg); 49 | expect(privateMessagingKeyFromHex(hex)).toEqual(test.arg); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/privateKeyEncrypter.ts: -------------------------------------------------------------------------------- 1 | import ed2curve from 'ed2curve'; 2 | import { idFromPrivateKey } from '../../multikey/ids'; 3 | import { EncryptedContent, Encrypter } from '../cipher'; 4 | import { RandomFunction, secureRandom } from '../../rand'; 5 | import { PrivateKey } from '../../private'; 6 | import { SECP256K1PrivateKey } from '../../secp256k1/private'; 7 | import { ED25519PrivateKey } from '../../ed25519/private'; 8 | import { serializePrivateKeyEncryptedContent } from './serialization'; 9 | import { easySeal } from './secretbox'; 10 | 11 | export class PrivateKeyEncrypter implements Encrypter { 12 | private _rand: RandomFunction; 13 | private _secretKey: Uint8Array; 14 | private _keyId: number; 15 | 16 | constructor(privateKey: PrivateKey, rand: RandomFunction = secureRandom) { 17 | this._rand = rand; 18 | this._keyId = idFromPrivateKey(privateKey); 19 | 20 | switch (privateKey.constructor) { 21 | case ED25519PrivateKey: 22 | this._secretKey = ed2curve.convertSecretKey(privateKey.bytes); 23 | break; 24 | case SECP256K1PrivateKey: 25 | this._secretKey = privateKey.bytes; 26 | break; 27 | default: 28 | throw RangeError('unknown private key type'); 29 | } 30 | } 31 | static fromPrivateKey(key: PrivateKey, rand: RandomFunction = secureRandom): PrivateKeyEncrypter { 32 | return new this(key, rand); 33 | } 34 | 35 | async encrypt(input: Uint8Array): Promise { 36 | const sealedBox = easySeal(input, this._secretKey, this._rand); 37 | 38 | return serializePrivateKeyEncryptedContent(sealedBox, this._keyId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/internal/src/transport/deliveryRequests/envelope.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import { 3 | ExtendedPrivateKey, 4 | PublicKey, 5 | RandomFunction, 6 | secureRandom, 7 | PrivateKeyEncrypter, 8 | ED25519PrivateKey, 9 | privateKeyToBytes, 10 | } from '@mailchain/crypto'; 11 | import { protocol } from '../../protobuf/protocol/protocol'; 12 | import { createECDHKeyBundle } from './keybundle'; 13 | 14 | /** 15 | * 16 | * @param recipientIdentityKey this is a Mailchain identity key. 17 | * @param messageKey root key used to encrypt message and payload 18 | */ 19 | export async function createEnvelope( 20 | recipientMessagingKey: PublicKey, 21 | messageRootEncryptionKey: ExtendedPrivateKey, 22 | messageURI: string, 23 | rand: RandomFunction = secureRandom, 24 | ): Promise { 25 | const keyBundle = await createECDHKeyBundle(recipientMessagingKey, rand); 26 | const encrypter = PrivateKeyEncrypter.fromPrivateKey(ED25519PrivateKey.fromSeed(keyBundle.secret), rand); 27 | const encryptedMessageKey = await encrypter.encrypt( 28 | privateKeyToBytes(messageRootEncryptionKey.privateKey as ED25519PrivateKey), 29 | ); //TODO: look into encoding extended keys 30 | const encryptedMessageURI = await encrypter.encrypt(Buffer.from(messageURI, 'utf8')); 31 | 32 | const payload = { 33 | encryptedMessageKey, 34 | encryptedMessageUri: encryptedMessageURI, 35 | ecdhKeyBundle: keyBundle.keyBundle, 36 | } as protocol.IEnvelope; 37 | 38 | const errMsg = protocol.Envelope.verify(payload); 39 | if (errMsg) throw Error(errMsg); 40 | 41 | return protocol.Envelope.create(payload); 42 | } 43 | -------------------------------------------------------------------------------- /packages/internal/src/migration.ts: -------------------------------------------------------------------------------- 1 | /** Generic rule for migration of data */ 2 | export type MigrationRule = { 3 | /** 4 | * Perform the required checks to check if migration is required for the provided `data`. 5 | * 6 | * Tip: better make this check fast without checking every aspect of the data because it might run multiple times. 7 | * If it ends up that migration is not required, just return the same data {@link MigrationRule.apply}. 8 | */ 9 | shouldApply: (data: T) => Promise; 10 | /** 11 | * Perform migration of the provided `data`, making sure not to mutate the input `data`. 12 | */ 13 | apply: (data: T) => Promise; 14 | }; 15 | 16 | /** No-operation migration. Useful when no migration is required and to provide this as default. */ 17 | export function nopMigration(): MigrationRule { 18 | return { 19 | shouldApply: () => Promise.resolve(false), 20 | apply: (data) => Promise.resolve(data), 21 | }; 22 | } 23 | 24 | /** Combine multiple migration rules into a single, where only required migrations will be applied. */ 25 | export function combineMigrations(...migrations: MigrationRule[]): MigrationRule { 26 | return { 27 | shouldApply: async (data) => { 28 | for (const migration of migrations) { 29 | if (await migration.shouldApply(data)) return true; 30 | } 31 | return false; 32 | }, 33 | apply: async (data) => { 34 | let latestData = data; 35 | for (const migration of migrations) { 36 | if (await migration.shouldApply(latestData)) { 37 | latestData = await migration.apply(latestData); 38 | } 39 | } 40 | return latestData; 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /packages/message-composer/src/fallback.ts: -------------------------------------------------------------------------------- 1 | import { HEADER_LABELS } from './consts'; 2 | import { createHeader, createMessageIdHeader } from './headerFactories'; 3 | import { MessageComposerContext } from './messageComposerContext'; 4 | import { Address, DateHeader, Header, MessageIdsHeader } from './types'; 5 | 6 | export async function concludeHeaders( 7 | headers: Map>, 8 | ctx: MessageComposerContext, 9 | ): Promise>> { 10 | const finalHeaders = new Map(headers); 11 | 12 | const fromHeader = finalHeaders.get(HEADER_LABELS.From); 13 | if (fromHeader == null) 14 | throw new Error('defining FROM is required, more info https://www.rfc-editor.org/rfc/rfc5322#section-3.6'); 15 | 16 | finalHeaders.set(HEADER_LABELS.MimeVersion, createHeader(HEADER_LABELS.MimeVersion, '1.0')); 17 | if (!finalHeaders.has(HEADER_LABELS.MessageId)) { 18 | finalHeaders.set(HEADER_LABELS.MessageId, await fallbackMessageId(fromHeader.value[0], ctx)); 19 | } 20 | if (!finalHeaders.has(HEADER_LABELS.Date)) { 21 | finalHeaders.set(HEADER_LABELS.Date, await fallbackDate(ctx)); 22 | } 23 | 24 | return finalHeaders; 25 | } 26 | 27 | export async function fallbackMessageId(from: Address, ctx: MessageComposerContext): Promise { 28 | const senderDomain = from.address.split('@')[1]; 29 | const idValue = await ctx.encodeBase64(await ctx.random(32)); 30 | return createMessageIdHeader(HEADER_LABELS.MessageId, [`${idValue}@${senderDomain}`]); 31 | } 32 | 33 | export async function fallbackDate(_ctx: MessageComposerContext): Promise { 34 | return createHeader(HEADER_LABELS.Date, new Date()); 35 | } 36 | -------------------------------------------------------------------------------- /packages/internal/src/transport/payload/verifier.test.ts: -------------------------------------------------------------------------------- 1 | import { aliceKeyRing, bobKeyRing } from '@mailchain/keyring/test.const'; 2 | import { ErrorPayloadSignatureInvalid, PayloadOriginVerifier } from './verifier'; 3 | 4 | describe('verifyPayloadOrigin', () => { 5 | it('verified', async () => { 6 | const verifier = new PayloadOriginVerifier(); 7 | const content = Buffer.from([12, 123, 4, 3, 14, 67]); 8 | 9 | verifier.verifyPayloadOrigin({ 10 | Content: content, 11 | Headers: { 12 | ContentEncoding: 'base64/plain', 13 | ContentEncryption: 'nacl-secret-key', 14 | ContentLength: 395, 15 | ContentSignature: await aliceKeyRing.accountMessagingKey().sign(content), 16 | ContentType: 'message/x.mailchain', 17 | Created: new Date('2022-07-13T18:44:48.536Z'), 18 | Origin: aliceKeyRing.accountMessagingKey().publicKey, 19 | }, 20 | }); 21 | }); 22 | 23 | it('invalid signature', async () => { 24 | const verifier = new PayloadOriginVerifier(); 25 | const content = Buffer.from([12, 123, 4, 3, 14, 67]); 26 | 27 | expect.assertions(1); 28 | await verifier 29 | .verifyPayloadOrigin({ 30 | Content: content, 31 | Headers: { 32 | ContentEncoding: 'base64/plain', 33 | ContentEncryption: 'nacl-secret-key', 34 | ContentLength: 395, 35 | ContentSignature: await aliceKeyRing.accountMessagingKey().sign(content), 36 | ContentType: 'message/x.mailchain', 37 | Created: new Date('2022-07-13T18:44:48.536Z'), 38 | Origin: bobKeyRing.accountMessagingKey().publicKey, // incorrect key 39 | }, 40 | }) 41 | .catch((e) => expect(e).toEqual(new ErrorPayloadSignatureInvalid())); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/addressing/src/protocols/tezos/const.ts: -------------------------------------------------------------------------------- 1 | export enum Prefix { 2 | TZ1 = 'tz1', 3 | TZ2 = 'tz2', 4 | TZ3 = 'tz3', 5 | KT = 'KT', 6 | KT1 = 'KT1', 7 | 8 | EDSK2 = 'edsk2', 9 | SPSK = 'spsk', 10 | P2SK = 'p2sk', 11 | 12 | EDPK = 'edpk', 13 | SPPK = 'sppk', 14 | P2PK = 'p2pk', 15 | 16 | EDESK = 'edesk', 17 | SPESK = 'spesk', 18 | P2ESK = 'p2esk', 19 | 20 | EDSK = 'edsk', 21 | EDSIG = 'edsig', 22 | SPSIG = 'spsig', 23 | P2SIG = 'p2sig', 24 | SIG = 'sig', 25 | EXPR = 'expr', 26 | } 27 | 28 | export const prefix = { 29 | [Prefix.TZ1]: new Uint8Array([6, 161, 159]), 30 | [Prefix.TZ2]: new Uint8Array([6, 161, 161]), 31 | [Prefix.TZ3]: new Uint8Array([6, 161, 164]), 32 | [Prefix.KT]: new Uint8Array([2, 90, 121]), 33 | [Prefix.KT1]: new Uint8Array([2, 90, 121]), 34 | 35 | [Prefix.EDSK]: new Uint8Array([43, 246, 78, 7]), 36 | [Prefix.EDSK2]: new Uint8Array([13, 15, 58, 7]), 37 | [Prefix.SPSK]: new Uint8Array([17, 162, 224, 201]), 38 | [Prefix.P2SK]: new Uint8Array([16, 81, 238, 189]), 39 | 40 | [Prefix.EDPK]: new Uint8Array([13, 15, 37, 217]), 41 | [Prefix.SPPK]: new Uint8Array([3, 254, 226, 86]), 42 | [Prefix.P2PK]: new Uint8Array([3, 178, 139, 127]), 43 | 44 | [Prefix.EDESK]: new Uint8Array([7, 90, 60, 179, 41]), 45 | [Prefix.SPESK]: new Uint8Array([0x09, 0xed, 0xf1, 0xae, 0x96]), 46 | [Prefix.P2ESK]: new Uint8Array([0x09, 0x30, 0x39, 0x73, 0xab]), 47 | 48 | [Prefix.EDSIG]: new Uint8Array([9, 245, 205, 134, 18]), 49 | [Prefix.SPSIG]: new Uint8Array([13, 115, 101, 19, 63]), 50 | [Prefix.P2SIG]: new Uint8Array([54, 240, 44, 52]), 51 | [Prefix.SIG]: new Uint8Array([4, 130, 43]), 52 | [Prefix.EXPR]: new Uint8Array([13, 44, 64, 27]), 53 | }; 54 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/nacl/secretbox.ts: -------------------------------------------------------------------------------- 1 | import nacl from 'tweetnacl'; 2 | import { RandomFunction } from '../../rand'; 3 | 4 | const nonceSize = 24; 5 | const secretKeySize = 32; 6 | 7 | export function easySeal(message: Uint8Array | Buffer, secretKey: Uint8Array, rand: RandomFunction): Uint8Array { 8 | const messagePrivate = message instanceof Buffer ? new Uint8Array(message) : message; // ensure it's a Uint8Array as secretbox checks instance of 9 | 10 | if (secretKey.length !== secretKeySize) { 11 | throw new RangeError('secret key must be 32 bytes'); 12 | } 13 | 14 | const nonce = rand(nonceSize); 15 | 16 | const encrypted = nacl.secretbox(messagePrivate, nonce, secretKey); 17 | const out = new Uint8Array(nonceSize + encrypted.length); 18 | out.set(nonce, 0); 19 | out.set(new Uint8Array(encrypted), nonceSize); 20 | 21 | return out; 22 | } 23 | 24 | export function easyOpen(sealedBox: Uint8Array, secretKey: Uint8Array): Uint8Array { 25 | const sealedBoxPrivate = sealedBox instanceof Buffer ? new Uint8Array(sealedBox) : sealedBox; // ensure it's a Uint8Array as secretbox checks instance of 26 | 27 | if (secretKey.length !== secretKeySize) { 28 | throw new RangeError('secret key must be 32 bytes'); 29 | } 30 | 31 | if (sealedBoxPrivate.length < nonceSize) { 32 | throw new RangeError('secret box must be longer than nonce'); 33 | } 34 | 35 | const nonce = sealedBoxPrivate.slice(0, nonceSize); 36 | const seal = sealedBoxPrivate.slice(nonceSize); 37 | 38 | const ret = nacl.secretbox.open(seal, nonce, secretKey); 39 | if (ret === null) { 40 | throw new Error('secretbox: could not decrypt data with private key'); 41 | } 42 | 43 | return ret; 44 | } 45 | -------------------------------------------------------------------------------- /packages/addressing/src/addressCasing.test.ts: -------------------------------------------------------------------------------- 1 | import { casingByProtocol } from './addressCasing'; 2 | import { ALGORAND, ETHEREUM, MAILCHAIN, NEAR, ProtocolType, SUBSTRATE } from './protocols'; 3 | 4 | const formatMailchainValueTests: { testName: string; value: string; protocol: ProtocolType; expected: string }[] = [ 5 | { 6 | testName: 'should have lower case for mailchain protocol', 7 | value: 'AlIcE', 8 | protocol: MAILCHAIN, 9 | expected: 'alice', 10 | }, 11 | { 12 | testName: 'should have lower case for ethereum protocol', 13 | value: '0x0E5736FBD198496Ef9A890a8D8be7538De9B2e0f', 14 | protocol: ETHEREUM, 15 | expected: '0x0e5736fbd198496ef9a890a8d8be7538de9b2e0f', 16 | }, 17 | { 18 | testName: 'should have mixed case for substrate protocol', 19 | value: '5F3sa2TJAWMqDhXG6jhV4N8ko9SxwGy8TpaNS1repo5EYjQX', 20 | protocol: SUBSTRATE, 21 | expected: '5F3sa2TJAWMqDhXG6jhV4N8ko9SxwGy8TpaNS1repo5EYjQX', 22 | }, 23 | { 24 | testName: 'should have lower case for algorand protocol', 25 | value: 'Z4X3PU5L6X3IPHE3CB5IEIS52TE5GU3GZFOOWW7BOTVXK5BCYR3444OQDW', 26 | protocol: ALGORAND, 27 | expected: 'z4x3pu5l6x3iphe3cb5ieis52te5gu3gzfooww7botvxk5bcyr3444oqdw', 28 | }, 29 | { 30 | testName: 'should have lower case for near protocol', 31 | value: '98793CD91A3F870FB126F66285808C7E094AFCFC4EDA8A970F6648CDF0DBD6DE', 32 | protocol: NEAR, 33 | expected: '98793cd91a3f870fb126f66285808c7e094afcfc4eda8a970f6648cdf0dbd6de', 34 | }, 35 | { testName: '', value: '', protocol: MAILCHAIN, expected: '' }, 36 | ]; 37 | 38 | test.each(formatMailchainValueTests)('$testName', ({ value, protocol, expected }) => { 39 | expect(casingByProtocol(value, protocol)).toEqual(expected); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/crypto/src/cipher/ecdh/ed25519.ts: -------------------------------------------------------------------------------- 1 | import nacl from 'tweetnacl'; 2 | import ed2curve from 'ed2curve'; 3 | import { KeyExchange } from '../'; 4 | import { RandomFunction, secureRandom } from '../../rand'; 5 | import { PublicKey } from '../../public'; 6 | import { PrivateKey } from '../../private'; 7 | import { ED25519PublicKey } from '../../ed25519/public'; 8 | import { ED25519PrivateKey, asED25519PrivateKey } from '../../ed25519/private'; 9 | 10 | export class ED25519KeyExchange implements KeyExchange { 11 | randomFunc: RandomFunction; 12 | constructor(randomFunc: RandomFunction = secureRandom) { 13 | this.randomFunc = randomFunc; 14 | } 15 | 16 | async EphemeralKey(): Promise { 17 | return ED25519PrivateKey.generate(this.randomFunc); 18 | } 19 | 20 | async SharedSecret(privateKey: PrivateKey, publicKey: PublicKey): Promise { 21 | if (privateKey.publicKey.bytes.toString() === publicKey.bytes.toString()) { 22 | throw new Error('public key can not be from private key'); 23 | } 24 | 25 | const publicKeyBytes = ED25519KeyExchange.publicKeyToCurve25519(publicKey); 26 | const privateKeyBytes = ED25519KeyExchange.privateKeyToCurve25519(asED25519PrivateKey(privateKey)); 27 | 28 | return nacl.scalarMult(privateKeyBytes, publicKeyBytes); 29 | } 30 | 31 | static privateKeyToCurve25519(privateKey: ED25519PrivateKey): Uint8Array { 32 | return ed2curve.convertSecretKey(privateKey.bytes); 33 | } 34 | 35 | static publicKeyToCurve25519(publicKey: ED25519PublicKey): Uint8Array { 36 | const output = ed2curve.convertPublicKey(publicKey.bytes); 37 | if (output === null) { 38 | throw new Error('invalid public key'); 39 | } 40 | return output; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/messageCrypto.ts: -------------------------------------------------------------------------------- 1 | import { EncryptedContent, ED25519ExtendedPrivateKey } from '@mailchain/crypto'; 2 | import { KeyRing } from '@mailchain/keyring'; 3 | import { deserialize, serialize, CHUNK_LENGTH_1MB, decryptPayload, encryptPayload } from '../transport/serialization'; 4 | import { Payload } from '../transport'; 5 | import { SerializablePayloadHeadersImpl } from '../transport/payload/headersSerialize'; 6 | 7 | /** 8 | * Used for encryption and decryption of the full message content. 9 | */ 10 | export type MessageCrypto = { 11 | encrypt: (payload: Payload) => Promise; 12 | decrypt: (content: EncryptedContent) => Promise; 13 | }; 14 | 15 | export function createMailchainMessageCrypto(keyRing: KeyRing): MessageCrypto { 16 | const inboxKey = ED25519ExtendedPrivateKey.fromPrivateKey(keyRing.rootInboxKey()); 17 | const headersSerializer = new SerializablePayloadHeadersImpl(); 18 | 19 | const encrypt: MessageCrypto['encrypt'] = async (payload) => { 20 | const headers = headersSerializer.serialize(payload.Headers); 21 | const encryptedPayload = await encryptPayload(headers, payload.Content, inboxKey, CHUNK_LENGTH_1MB); 22 | const serializedContent = serialize(encryptedPayload); 23 | return new Uint8Array(serializedContent); 24 | }; 25 | 26 | const decrypt: MessageCrypto['decrypt'] = async (serializedContent) => { 27 | const encryptedPayload = deserialize(Buffer.from(serializedContent)); 28 | const { headers, content } = await decryptPayload(encryptedPayload, inboxKey); 29 | 30 | return { 31 | Content: content, 32 | Headers: headersSerializer.deserialize(headers), 33 | }; 34 | }; 35 | 36 | return { encrypt, decrypt }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/internal/src/mailbox/messageCrypto.test.ts: -------------------------------------------------------------------------------- 1 | import { aliceKeyRing, bobKeyRing } from '@mailchain/keyring/test.const'; 2 | import { KindNaClSecretKey, secureRandom } from '@mailchain/crypto'; 3 | import { BobED25519PublicKey } from '@mailchain/crypto/ed25519/test.const'; 4 | import { EncodingTypes } from '@mailchain/encoding'; 5 | import { Payload } from '../transport'; 6 | import { createMailchainMessageCrypto } from './messageCrypto'; 7 | 8 | const testPayload: Payload = { 9 | Content: Buffer.from(secureRandom(20 * 1024)), // 20KB, 10 | Headers: { 11 | Origin: BobED25519PublicKey, 12 | ContentSignature: secureRandom(), 13 | Created: new Date('11.27.2023'), 14 | ContentLength: 10 * 1024, 15 | ContentType: 'message/x.mailchain', 16 | ContentEncoding: EncodingTypes.Base64, 17 | ContentEncryption: KindNaClSecretKey, 18 | }, 19 | }; 20 | 21 | describe('createMailchainMessageCrypto', () => { 22 | beforeEach(() => { 23 | jest.clearAllMocks(); 24 | }); 25 | 26 | it('should roundtrip', async () => { 27 | const messageCrypto = createMailchainMessageCrypto(aliceKeyRing); 28 | 29 | const encryptedPayload = await messageCrypto.encrypt(testPayload); 30 | const decryptedPayload = await messageCrypto.decrypt(encryptedPayload); 31 | 32 | expect(decryptedPayload).toEqual(testPayload); 33 | }); 34 | 35 | it('should fail roundtrip with different keys', async () => { 36 | const messageCrypto = createMailchainMessageCrypto(aliceKeyRing); 37 | 38 | const encryptedPayload = await messageCrypto.encrypt(testPayload); 39 | 40 | const messageCrypto2 = createMailchainMessageCrypto(bobKeyRing); 41 | await expect(messageCrypto2.decrypt(encryptedPayload)).rejects.toThrowError(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/api/src/helpers/apiKeyToCryptoKey.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PrivateKey, 3 | PublicKey, 4 | ED25519PrivateKey, 5 | ED25519PublicKey, 6 | SECP256K1PublicKey, 7 | SECP256K1PrivateKey, 8 | } from '@mailchain/crypto'; 9 | import { decode } from '@mailchain/encoding'; 10 | import { SECP256R1PrivateKey, SECP256R1PublicKey } from '@mailchain/crypto/secp256r1'; 11 | import { 12 | PrivateKey as ApiPrivateKey, 13 | PrivateKeyCurveEnum, 14 | PublicKey as ApiPublicKey, 15 | PublicKeyCurveEnum, 16 | } from '../api/api'; 17 | import { ErrorUnsupportedKey } from './errors'; 18 | 19 | /** Convert {@link ApiPublicKey} to {@link PublicKey} */ 20 | export function convertPublic(key: ApiPublicKey): PublicKey { 21 | switch (key.curve) { 22 | case PublicKeyCurveEnum.Ed25519: 23 | return new ED25519PublicKey(decode(key.encoding, key.value)); 24 | case PublicKeyCurveEnum.Secp256k1: 25 | return new SECP256K1PublicKey(decode(key.encoding, key.value)); 26 | case PublicKeyCurveEnum.Secp256r1: 27 | return new SECP256R1PublicKey(decode(key.encoding, key.value)); 28 | default: 29 | throw new ErrorUnsupportedKey(key.curve); 30 | } 31 | } 32 | 33 | /** Convert {@link ApiPrivateKey} to {@link PrivateKey} */ 34 | export function convertPrivate(key: ApiPrivateKey): PrivateKey { 35 | switch (key.curve) { 36 | case PrivateKeyCurveEnum.Ed25519: 37 | return ED25519PrivateKey.fromSecretKey(decode(key.encoding, key.value)); 38 | case PrivateKeyCurveEnum.Secp256k1: 39 | return new SECP256K1PrivateKey(decode(key.encoding, key.value)); 40 | case PrivateKeyCurveEnum.Secp256r1: 41 | return new SECP256R1PrivateKey(decode(key.encoding, key.value)); 42 | default: 43 | throw new ErrorUnsupportedKey(key.curve); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/encoding/src/base32.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeBase32, encodeBase32 } from './base32'; 2 | describe('Buffer', () => { 3 | it('Encode and decode are the same', () => { 4 | const arr = new Uint8Array(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 127, 125, 0, 1])); 5 | expect(decodeBase32(encodeBase32(arr))).toEqual(arr); 6 | }); 7 | }); 8 | 9 | describe('DecodeBase32', () => { 10 | const tests = [ 11 | { 12 | input: 'e7yj7gfs6cp2lm7qt6mlf4e7uwz7bh4zqiytgmrte4', 13 | expected: Uint8Array.from([ 14 | 0x27, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 15 | 0xf0, 0x9f, 0x99, 0x82, 0x31, 0x33, 0x32, 0x33, 0x27, 16 | ]), 17 | }, 18 | { 19 | input: 'me', 20 | expected: Uint8Array.from([0x61]), 21 | }, 22 | { 23 | input: 'jbswy3dp', 24 | expected: Uint8Array.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]), 25 | }, 26 | ]; 27 | 28 | test.each(tests)('$name', async (test) => { 29 | expect(decodeBase32(test.input)).toEqual(test.expected); 30 | }); 31 | }); 32 | 33 | describe('EncodeBase32', () => { 34 | const tests = [ 35 | { 36 | input: Uint8Array.from([ 37 | 0x27, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 0xf0, 0x9f, 0x98, 0xb2, 0xf0, 0x9f, 0xa5, 0xb3, 38 | 0xf0, 0x9f, 0x99, 0x82, 0x31, 0x33, 0x32, 0x33, 0x27, 39 | ]), 40 | expected: 'e7yj7gfs6cp2lm7qt6mlf4e7uwz7bh4zqiytgmrte4', 41 | }, 42 | { 43 | input: Uint8Array.from([0x61]), 44 | expected: 'me', 45 | }, 46 | { 47 | input: Uint8Array.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]), 48 | expected: 'jbswy3dp', 49 | }, 50 | ]; 51 | 52 | test.each(tests)('$name', async (test) => { 53 | expect(encodeBase32(test.input)).toEqual(test.expected); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/internal/src/verifiableCredentials/did.ts: -------------------------------------------------------------------------------- 1 | import { ProtocolType } from '@mailchain/addressing'; 2 | 3 | export type DecentralizedIdentifier = 4 | | MailchainDecentralizedIdentifier 5 | | MailchainMessagingKeyDecentralizedIdentifier; 6 | 7 | export type MailchainDecentralizedIdentifier = `did:mailchain:${S}`; 8 | 9 | export function mailchainAddressDecentralizedIdentifier(address: string): MailchainDecentralizedIdentifier { 10 | return `did:mailchain:${encodeURIComponent(address)}`; 11 | } 12 | 13 | export function isMailchainAddressDecentralizedIdentifier(did: string): did is MailchainDecentralizedIdentifier { 14 | return did.startsWith('did:mailchain:'); 15 | } 16 | 17 | export function mailchainAddressFromDecentralizedIdentifier(did: MailchainDecentralizedIdentifier): string { 18 | return decodeURIComponent(did.replace('did:mailchain:', '')); 19 | } 20 | 21 | export type MailchainMessagingKeyDecentralizedIdentifier = 22 | `did:mailchain:${S}/messaging-key`; 23 | 24 | export function mailchainMessagingKeyDecentralizedIdentifier( 25 | address: string, 26 | ): MailchainMessagingKeyDecentralizedIdentifier { 27 | return `${mailchainAddressDecentralizedIdentifier(address)}/messaging-key`; 28 | } 29 | 30 | export type MailchainBlockchainAddressDecentralizedIdentifier< 31 | P extends ProtocolType = ProtocolType, 32 | S extends string = string, 33 | > = `did:mailchain:${P}:${S}`; 34 | 35 | export function mailchainBlockchainAddressDecentralizedIdentifier

( 36 | protocol: P, 37 | protocolAddress: S, 38 | ): MailchainBlockchainAddressDecentralizedIdentifier { 39 | return `did:mailchain:${protocol}:${protocolAddress}`; 40 | } 41 | -------------------------------------------------------------------------------- /packages/internal/src/sending/mail/payloads.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithPublicKey } from '@mailchain/crypto'; 2 | import { Distribution, MailData, Payload } from '../../transport'; 3 | import { ResolvedAddress } from '../../messagingKeys'; 4 | import { createMimeMessage } from '../../formatters/generate'; 5 | import { createPayload } from '../payload'; 6 | 7 | export async function createMailPayloads( 8 | senderMessagingKey: SignerWithPublicKey, 9 | resolvedAddresses: Map, 10 | mailData: MailData, 11 | payloadPluginHeaders?: Record, 12 | ): Promise<{ 13 | original: Payload; 14 | distributions: Distribution[]; 15 | }> { 16 | const message = await createMimeMessage(mailData, resolvedAddresses); 17 | 18 | const original = await createPayload( 19 | senderMessagingKey, 20 | Buffer.from(message.original), 21 | 'message/x.mailchain', 22 | payloadPluginHeaders, 23 | ); 24 | const visibleRecipientsPayload = await createPayload( 25 | senderMessagingKey, 26 | Buffer.from(message.visibleRecipients), 27 | 'message/x.mailchain', 28 | payloadPluginHeaders, 29 | ); 30 | const blindRecipients = await Promise.all( 31 | message.blindRecipients.map(async ({ recipient, content }) => ({ 32 | recipients: [recipient.address], 33 | payload: await createPayload( 34 | senderMessagingKey, 35 | Buffer.from(content), 36 | 'message/x.mailchain', 37 | payloadPluginHeaders, 38 | ), 39 | })), 40 | ); 41 | 42 | return { 43 | original, 44 | distributions: [ 45 | { 46 | recipients: [ 47 | ...mailData.recipients.map((x) => x.address), 48 | ...mailData.carbonCopyRecipients.map((x) => x.address), 49 | ], 50 | payload: visibleRecipientsPayload, 51 | }, 52 | ...blindRecipients, 53 | ], 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /packages/api/src/helpers/cryptoKeyToApiKey.ts: -------------------------------------------------------------------------------- 1 | import { KindED25519, KindSECP256K1, KindSECP256R1, PrivateKey, PublicKey } from '@mailchain/crypto'; 2 | import { encodeHexZeroX } from '@mailchain/encoding'; 3 | import { 4 | PublicKey as ApiPublicKey, 5 | PrivateKey as ApiPrivateKey, 6 | PublicKeyCurveEnum, 7 | PrivateKeyCurveEnum, 8 | } from '../api/api'; 9 | import { ErrorUnsupportedKey } from './errors'; 10 | 11 | /** Convert {@link PublicKey} to {@link ApiPublicKey}. */ 12 | export function convertPublic(key: PublicKey): ApiPublicKey { 13 | switch (key.curve) { 14 | case KindED25519: 15 | return { curve: PublicKeyCurveEnum.Ed25519, value: encodeHexZeroX(key.bytes), encoding: 'hex/0x-prefix' }; 16 | case KindSECP256K1: 17 | return { curve: PublicKeyCurveEnum.Secp256k1, value: encodeHexZeroX(key.bytes), encoding: 'hex/0x-prefix' }; 18 | case KindSECP256R1: 19 | return { curve: PublicKeyCurveEnum.Secp256r1, value: encodeHexZeroX(key.bytes), encoding: 'hex/0x-prefix' }; 20 | default: 21 | throw new ErrorUnsupportedKey(key.curve); 22 | } 23 | } 24 | 25 | /** Convert {@link PrivateKey} to {@link ApiPrivateKey} */ 26 | export function convertPrivate(key: PrivateKey): ApiPrivateKey { 27 | switch (key.curve) { 28 | case KindED25519: 29 | return { curve: PrivateKeyCurveEnum.Ed25519, value: encodeHexZeroX(key.bytes), encoding: 'hex/0x-prefix' }; 30 | case KindSECP256K1: 31 | return { 32 | curve: PrivateKeyCurveEnum.Secp256k1, 33 | value: encodeHexZeroX(key.bytes), 34 | encoding: 'hex/0x-prefix', 35 | }; 36 | case KindSECP256R1: 37 | return { 38 | curve: PrivateKeyCurveEnum.Secp256r1, 39 | value: encodeHexZeroX(key.bytes), 40 | encoding: 'hex/0x-prefix', 41 | }; 42 | default: 43 | throw new ErrorUnsupportedKey(key.curve); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/internal/src/transport/payload/headers.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@mailchain/crypto'; 2 | 3 | export type ContentType = 4 | | 'application/json' 5 | | 'message/x.mailchain' 6 | | 'message/x.mailchain-mailer' 7 | | 'application/vnd.mailchain.verified-credential-request'; 8 | 9 | /** 10 | * PayloadHeaders are the headers provide information about the payload. 11 | */ 12 | export type PayloadHeaders = { 13 | /** 14 | * public key of sender to verify the contents 15 | */ 16 | Origin: PublicKey; 17 | /** 18 | * used to verify un-encrypted contents 19 | */ 20 | ContentSignature: Uint8Array; 21 | 22 | Created: Date; 23 | 24 | // Standard 25 | 26 | /** 27 | * The size of the resource, in decimal number of bytes. 28 | * The Content-Length header indicates the size of the message body, in bytes, sent to the recipient. 29 | */ 30 | ContentLength: number; 31 | 32 | /** 33 | * Indicates the media type of the resource. 34 | * The ContentType representation header is used to indicate the original media type of the resource (prior to any content encoding and encryption applied for sending). 35 | * In responses, a ContentType header provides the client with the actual content type of the returned content. 36 | */ 37 | ContentType: CT; 38 | 39 | /** 40 | * Used to specify the compression algorithm. 41 | */ 42 | ContentEncoding: string; 43 | 44 | /** 45 | * Used to specify the encryption algorithm. 46 | */ 47 | ContentEncryption: string; 48 | 49 | /** 50 | * Indicates an alternate location for the contents if not contained in this object. 51 | */ 52 | ContentLocation?: string; 53 | /** 54 | * Custom set of headers used by plugins. 55 | */ 56 | PluginHeaders?: { 57 | [x: string]: unknown; 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /packages/message-composer/Readme.md: -------------------------------------------------------------------------------- 1 | # Mailchain Message Composer 2 | 3 | > **Warning** `@mailchain/message-composer` library is in development and may not cover all cases yet. Use with caution. 4 | 5 | RFC-5322 compliant MIME message composer for Node and for the browser. Just add the content you want and let the library take care of formatting it according to the specification. 6 | 7 | ## Usage 8 | 9 | ```ts 10 | const msg = createMessageComposer(messageComposerContext) 11 | // .id('47bdaf40-c3da-49f7-bfc7-66337f35c6c1@mailchain.com') // Optional 12 | .subject('Subject can contain UTF-8 chars including emojis 😉') 13 | // .date(new Date('08/28/2022')) // Optional 14 | .from({ name: 'Bob', address: 'bob@mailchain.com' }) 15 | .recipients('To', { name: 'Alice', address: 'alice@mailchain.com' }) 16 | .recipients('Cc', { name: 'Joe Doe', address: 'joe@mailchain.com' }) 17 | .recipients( 18 | 'Bcc', 19 | { name: 'Jane Doe', address: 'jane@mailchain.com' }, 20 | { name: 'Bob', address: 'bob@mailchain.com' }, 21 | ) 22 | .message('plain', Buffer.from('Plaintext content. Can also contain UTF-8 and emojis 🤐.')) 23 | .message('html', Buffer.from('This is ✨rich-text✨ HTML content.')) 24 | .attachment({ 25 | cid: 'bfcd3a31-646b-4dcd-a1ea-06d37baf7d2e', 26 | contentType: 'image/png', 27 | filename: 'mailchain-logo.png', 28 | content: readFileSync('mailchain-logo.png'), 29 | }); 30 | 31 | const { forSender, forVisibleRecipients, forBlindedRecipients } = await msg.build(); 32 | ``` 33 | 34 | For full usage examples view the [developer docs](https://docs.mailchain.com). 35 | 36 | ## Installing 37 | 38 | Using npm: 39 | 40 | ```bash 41 | $ npm install @mailchain/message-composer 42 | ``` 43 | 44 | Using yarn: 45 | 46 | ```bash 47 | $ yarn add @mailchain/message-composer 48 | ``` 49 | -------------------------------------------------------------------------------- /packages/sdk/src/internal/keys.ts: -------------------------------------------------------------------------------- 1 | import { decodeHexAny, encodeHex } from '@mailchain/encoding'; 2 | import { 3 | PrivateKey, 4 | PublicKey, 5 | privateKeyFromBytes, 6 | privateKeyToBytes, 7 | publicKeyFromBytes, 8 | publicKeyToBytes, 9 | } from '@mailchain/crypto'; 10 | 11 | /** 12 | * Converts a private messaging key from its hexadecimal representation to a {@link PrivateKey} object. 13 | * @param hex - The hexadecimal representation of the private messaging key. 14 | * @returns The {@link PrivateKey} object. 15 | */ 16 | export function privateMessagingKeyFromHex(hex: string): PrivateKey { 17 | return privateKeyFromBytes(decodeHexAny(hex)); 18 | } 19 | 20 | /** 21 | * Converts a private messaging key from a {@link PrivateKey} object to its hexadecimal representation. 22 | * @param key - The {@link PrivateKey} object. 23 | * @returns The hexadecimal representation of the private messaging key. 24 | */ 25 | export function privateMessagingKeyToHex(key: PrivateKey): string { 26 | return encodeHex(privateKeyToBytes(key)); 27 | } 28 | 29 | /** 30 | * Converts a public messaging key from its hexadecimal representation to a {@link PublicKey} object. 31 | * @param hex - The hexadecimal representation of the public messaging key. 32 | * @returns The {@link PublicKey} object. 33 | */ 34 | export function publicMessagingKeyFromHex(hex: string): PublicKey { 35 | return publicKeyFromBytes(decodeHexAny(hex)); 36 | } 37 | 38 | /** 39 | * Converts a public messaging key from a {@link PublicKey} object to its hexadecimal representation. 40 | * @param key - The {@link PublicKey} object. 41 | * @returns The hexadecimal representation of the public messaging key. 42 | */ 43 | export function publicMessagingKeyToHex(key: PublicKey): string { 44 | return encodeHex(publicKeyToBytes(key)); 45 | } 46 | -------------------------------------------------------------------------------- /packages/encoding/src/hex.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeHexZeroX, encodeHexZeroX } from './hex'; 2 | 3 | describe('EncodeHexZeroX', () => { 4 | const tests = [ 5 | { 6 | name: 'success', 7 | input: new Uint8Array([ 8 | 0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 9 | 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61, 10 | ]), 11 | expected: '0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761', 12 | shouldThrow: false, 13 | }, 14 | ]; 15 | test.each(tests)('$name', async (test) => { 16 | const target = encodeHexZeroX; 17 | 18 | if (test.shouldThrow) { 19 | expect(() => { 20 | target(test.input); 21 | }).toThrow(); 22 | } else { 23 | expect(target(test.input)).toEqual(test.expected); 24 | } 25 | }); 26 | }); 27 | 28 | describe('DecodeHexZeroX', () => { 29 | const tests = [ 30 | { 31 | name: 'success', 32 | input: '0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761', 33 | expected: new Uint8Array([ 34 | 0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 35 | 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61, 36 | ]), 37 | shouldThrow: false, 38 | }, 39 | { 40 | name: 'err-missing-prefix', 41 | input: '5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761', 42 | expected: null, 43 | shouldThrow: true, 44 | }, 45 | ]; 46 | test.each(tests)('$name', async (test) => { 47 | const target = decodeHexZeroX; 48 | 49 | if (test.shouldThrow) { 50 | expect(() => { 51 | target(test.input); 52 | }).toThrow(); 53 | } else { 54 | expect(target(test.input)).toEqual(test.expected); 55 | } 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/encoding/src/hex.ts: -------------------------------------------------------------------------------- 1 | // EncodeHexZeroX encodes src into "0x"+hex.Encode. As a convenience, it returns the encoding type used, 2 | // but this value is always TypeHex0XPrefix. 3 | // EncodeHexZeroX uses hexadecimal encoding prefixed with "0x". 4 | export function encodeHexZeroX(input: Uint8Array): string { 5 | return '0x' + encodeHex(input); 6 | } 7 | 8 | // DecodeHexZeroX returns the bytes represented by the hexadecimal string src. 9 | export function decodeHexZeroX(input: string): Uint8Array { 10 | if (input === '') { 11 | throw new RangeError('empty hex string'); 12 | } 13 | 14 | if (!input.startsWith('0x')) { 15 | throw new RangeError("must start with '0x'"); 16 | } 17 | 18 | return decodeHex(input.slice(2)); 19 | } 20 | 21 | // EncodeHex returns the hexadecimal encoding of src. 22 | export function encodeHex(input: Uint8Array): string { 23 | return Buffer.from(input).toString('hex'); 24 | } 25 | 26 | // DecodeHex returns the bytes represented by the hexadecimal string s. 27 | export function decodeHex(input: string): Uint8Array { 28 | const output = new Uint8Array(Buffer.from(input, 'hex')); 29 | if (output.length === 0 || input.length === 0) { 30 | throw RangeError('invalid hex encoding'); 31 | } 32 | return output; 33 | } 34 | 35 | export function isHex(input: string): boolean { 36 | return new RegExp('^[a-fA-F0-9]+$').test(input); 37 | } 38 | 39 | export function isHexZeroX(input: string): boolean { 40 | return new RegExp('^0x[a-fA-F0-9]+$').test(input); 41 | } 42 | 43 | export function isAnyHex(input: string): boolean { 44 | if (input.startsWith('0x')) { 45 | input = input.substring(2); 46 | } 47 | return isHex(input); 48 | } 49 | 50 | export function decodeHexAny(input: string): Uint8Array { 51 | if (input.startsWith('0x')) { 52 | input = input.substring(2); 53 | } 54 | return decodeHex(input); 55 | } 56 | --------------------------------------------------------------------------------