@bicycle-codes/ailuropoda

ailuropoda

tests types module semantic versioning license

Implementing bamboo, using only browser compatible cryptography.

Ailuropoda is the science name for a panda.

npm i -S @bicycle-codes/ailuropoda

Import types and functions.

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

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

interface Metadata {
timestamp:number;
proof:string,
key:string, // <-- base64url encoded
seq:number;
lipmaalink:string|null;
prev:string|null;
username:string;
author:DID;
}
import { SignedMessage } from '@bicycle-codes/message'

type SignedMetadata = SignedMessage<Metadata>
export interface Content {
text:string,
alt?:string[],
mentions?:string[]
}
type SignedPost = { metadata:SignedMetadata, content:Content }

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

Get the lipmaa number number given a sequence number.

function lipmaaLink (n:number):number
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 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'
}
}
)

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

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]

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 ]

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[]>

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
}

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>
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' }
})

Generated via typescript.

bicycle-codes.github.io/ailuropoda