Skip to content

bicycle-codes/ailuropoda

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ailuropoda

tests types module semantic versioning license

Implementing bamboo, using only browser compatible cryptography.

Ailuropoda is the science name for a panda.

install

npm i -S @bicycle-codes/ailuropoda

use

Import types and functions.

import {
    create as createMsg,
    SignedPost,
    lipmaaLink,
    createBatch,
    getLipmaaPath,
    isValid,
    verifyLipmaas
} from '@bicycle-codes/ailuropoda'

data format

Log entries are { metadata, content }, where metadata is a signed object like below.

Metadata

interface Metadata {
    timestamp:number;
    proof:string,
    key:string,  // <-- base64url encoded
    seq:number;
    lipmaalink:string|null;
    prev:string|null;
    username:string;
    author:DID;
}

SignedMetadata

import { SignedMessage } from '@bicycle-codes/message'

type SignedMetadata = SignedMessage<Metadata>

Content

export interface Content {
    text:string,
    alt?:string[],
    mentions?:string[]
}

SignedPost

type SignedPost = { metadata:SignedMetadata, content:Content }

example

Use the function createBatch to create a list with lipmaa links.

See the diagram for a nice visualization of the list structure.

import { Identity, create as createID } from '@bicycle-codes/identity'
import { createCryptoComponent } from '@ssc-half-light/node-components'
import { createBatch } from '@bicycle-codes/ailuropoda'

const alicesCrytpo = await createCryptoComponent()
const alice = await createID(alicesCrytpo, {
    humanName: 'alice',
    humanReadableDeviceName: 'computer'
})

const newMsgs = [
    { content: { text: 'hello 1' } },
    { content: { text: 'hello 2' } },
    { content: { text: 'hello 3' } },
    { content: { text: 'hello 4' } },
    { content: { text: 'hello 5' } }
]

const list = await createBatch(alice, alicesCrytpo, {
    // we are just using an in-memory array of messages
    getKeyFromIndex: async (i:number, msgs:SignedPost[]) => {
        const msg = msgs[i]
        if (!msg) return null
        return msg.metadata.key
    }
}, newMsgs)  // pass in a list with message content

API

lipmaaLink (n)

Get the lipmaa number number given a sequence number.

function lipmaaLink (n:number):number

lipmaaLink example

const lipmaas = ([...Array(41).keys()]).map(n => {
    return { lipmaa: lipmaaLink(n), n }
})

lipmaas is like this:

 [
    { lipmaa: 0, n: 0 },   { lipmaa: 0, n: 1 },
    { lipmaa: 1, n: 2 },   { lipmaa: 2, n: 3 },
    { lipmaa: 1, n: 4 },   { lipmaa: 4, n: 5 },
    { lipmaa: 5, n: 6 },   { lipmaa: 6, n: 7 },
    { lipmaa: 4, n: 8 },   { lipmaa: 8, n: 9 },
    { lipmaa: 9, n: 10 },  { lipmaa: 10, n: 11 },
    { lipmaa: 8, n: 12 },  { lipmaa: 4, n: 13 },
    { lipmaa: 13, n: 14 }, { lipmaa: 14, n: 15 },
    { lipmaa: 15, n: 16 }, { lipmaa: 13, n: 17 },
    { lipmaa: 17, n: 18 }, { lipmaa: 18, n: 19 },
    { lipmaa: 19, n: 20 }, { lipmaa: 17, n: 21 },
    { lipmaa: 21, n: 22 }, { lipmaa: 22, n: 23 },
    { lipmaa: 23, n: 24 }, { lipmaa: 21, n: 25 },
    { lipmaa: 13, n: 26 }, { lipmaa: 26, n: 27 },
    { lipmaa: 27, n: 28 }, { lipmaa: 28, n: 29 },
    { lipmaa: 26, n: 30 }, { lipmaa: 30, n: 31 },
    { lipmaa: 31, n: 32 }, { lipmaa: 32, n: 33 },
    { lipmaa: 30, n: 34 }, { lipmaa: 34, n: 35 },
    { lipmaa: 35, n: 36 }, { lipmaa: 36, n: 37 },
    { lipmaa: 34, n: 38 }, { lipmaa: 26, n: 39 },
    { lipmaa: 13, n: 40 }
]

Note the lipmaa vs n properties match with this diagram.

lipmaa diagram

create (user, crypto, opts)

Create a message. This does not deal with lipmaa links. You would need to pass them in.

async function create (
    user:Identity,
    crypto:Implementation,
    opts:{
        content:Content,
        limpaalink?:string|null,  // <-- the key of the lipmaa message
        seq:number,
        prev:SignedPost|null|undefined,
    }
):Promise<SignedPost>
import { create as createMsg } from '@bicycle-codes/ailuropoda'

const post = await createMsg(
    alice,
    alicesCrytpo,
    {
        seq: 1,
        prev: null,
        content: {
            text: 'hello'
        }
    }
)

isValid (message)

Verify a message. This does not look at links, only the signature and hash.

async function isValid (msg:SignedPost):Promise<boolean>
const { isOk } = await isValid(post)
// => true

verifyLipmaas ({ messageFromKey }, msg, path)

Check that all the messages between the given message and message number 1 are valid. This will use the shortest path from the given message to the first message.

async function verifyLipmaas ({
    messageFromKey
}:{
    messageFromKey:(key:string)=>Promise<SignedPost>
}, msg:SignedPost, path?:number[]):Promise<{
    isOk: boolean,
    path:number[]
}>
const { isOk, path } = await verifyLipmaas(list2, {
    messageFromKey
}, list2[39])  // array is 0 indexed, so 39 is seq number 40

// isOk = true
// path = [40, 13, 4, 1]

getLipmaaPath (seq, prev)

Get the shortest path between the given sequence number and the first message. The parameter prev is used internally, for recusion.

function getLipmaaPath (seq:number, prev?:number[]):number[]

Return an array of sequence numbers, starting with the first:

[ 1, 4, 13 ]

createBatch (user, crypto, opts, messages)

Create a linked list of the given messages, with lipmaa links.

async function createBatch (
    user:Identity,
    crypto:Implementation,
    opts: {
        getKeyFromIndex:(i:number, msgs:SignedPost[]) => Promise<string|null>
    },
    msgs:{
        content:Content,
        seq?:number,
        prev?:SignedPost|null|undefined,
    }[],
    _out?:SignedPost[]
):Promise<SignedPost[]>

createBatch example

Create a linked list with in-memory content, starting from entry number 1.

Note in the example, getKey is synchronous, but we need to return a promise because that's what the API expects.

Takes a parameter getKeyFromIndex that will return the key for an entry given its index.

const newMsgs = [
    { content: { text: 'hello 1' } },
    { content: { text: 'hello 2' } },
    { content: { text: 'hello 3' } },
    { content: { text: 'hello 4' } },
    { content: { text: 'hello 5' } }
]

const list = await createBatch(alice, alicesCrytpo, {
    getKeyFromIndex: getKey
}, newMsgs)

async function getKey (i:number, msgs:SignedPost[]):Promise<string|null> {
    const msg = msgs[i]
    if (!msg) return null
    return msg.metadata.key
}

append (user, crypto, opts)

Given a previous message and a function that will return a message by its sequence number, create a new message with the correct lipmaa key.

async function append (
    user:Identity,
    crypto:Implementation,
    opts:{
        getBySeq:(seq:number) => Promise<SignedPost>
        content:Content,
        prev:SignedPost
    }
):Promise<SignedPost>

append example

const list = await createBatch(alice, alicesCrytpo, {
    getKeyFromIndex: async (i, msgs) => {
        return msgs[i].metadata.key
    },
}, msgs)

const newMsg = await append(alice, alicesCrytpo, {
    getBySeq: async (seq) => {
        return list[seq - 1]  // 0 vs 1 indexed
    },
    prev: list[list.length - 1],
    content: { text: 'hello 40' }
})

docs

Generated via typescript.

bicycle-codes.github.io/ailuropoda