Skip to content

Commit

Permalink
Copy bip68 library code and adapt it to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
rkalis committed Oct 29, 2024
1 parent e9e1bab commit 188537d
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 25 deletions.
1 change: 0 additions & 1 deletion packages/cashscript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"@bitauth/libauth": "^3.0.0",
"@cashscript/utils": "^0.10.1",
"@mr-zwets/bchn-api-wrapper": "^1.0.1",
"bip68": "^1.0.4",
"delay": "^5.0.0",
"electrum-cash": "^2.0.10",
"fast-deep-equal": "^3.1.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/cashscript/src/Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import bip68 from 'bip68';
import {
hexToBin,
decodeTransaction,
Expand All @@ -8,6 +7,7 @@ import {
import delay from 'delay';
import {
AbiFunction,
encodeBip68,
placeholder,
scriptToBytecode,
} from '@cashscript/utils';
Expand Down Expand Up @@ -114,7 +114,7 @@ export class Transaction {
}

withAge(age: number): this {
this.sequence = bip68.encode({ blocks: age });
this.sequence = encodeBip68({ blocks: age });
return this;
}

Expand Down
1 change: 0 additions & 1 deletion packages/cashscript/src/external-types/bip68.d.ts

This file was deleted.

60 changes: 60 additions & 0 deletions packages/utils/src/bip68.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Code taken and adapted from https://github.com/bitcoinjs/bip68
// If we make signficant changes to this code, we should also take and adapt the tests from that repository.

// see https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki#compatibility

const SEQUENCE_FINAL = 0xffffffff;
const SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);
const SEQUENCE_LOCKTIME_GRANULARITY = 9;
const SEQUENCE_LOCKTIME_MASK = 0x0000ffff;
const SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);

const BLOCKS_MAX = SEQUENCE_LOCKTIME_MASK;
const SECONDS_MOD = 1 << SEQUENCE_LOCKTIME_GRANULARITY;
const SECONDS_MAX = SEQUENCE_LOCKTIME_MASK << SEQUENCE_LOCKTIME_GRANULARITY;

interface DecodedSequence {
blocks?: number;
seconds?: number;
}

export function decodeBip68(sequence: number): DecodedSequence {
// If the disable flag is set, we return an empty object
if (sequence & SEQUENCE_LOCKTIME_DISABLE_FLAG) return {};

// If the SEQUENCE_LOCKTIME_TYPE_FLAG is set, that means that the sequence is in seconds
if (sequence & SEQUENCE_LOCKTIME_TYPE_FLAG) {
// If the sequence is in seconds, we need to shift it by the granularity
// (because every "unit" of time corresponds to 512 seconds)
const seconds = (sequence & SEQUENCE_LOCKTIME_MASK) << SEQUENCE_LOCKTIME_GRANULARITY;
return { seconds };
}

// If the disable flag is not set, and the SEQUENCE_LOCKTIME_TYPE_FLAG is not set, the sequence is in blocks
const blocks = sequence & SEQUENCE_LOCKTIME_MASK;
return { blocks };
}

export function encodeBip68({ blocks, seconds }: DecodedSequence): number {
if (blocks !== undefined && seconds !== undefined) throw new TypeError('Cannot encode blocks AND seconds');

// If the input is correct, we encode it as a sequence in seconds (using the SEQUENCE_LOCKTIME_TYPE_FLAG)
if (seconds !== undefined) {
if (!Number.isFinite(seconds)) throw new TypeError('Expected Number seconds');
if (seconds > SECONDS_MAX) throw new TypeError('Expected Number seconds <= ' + SECONDS_MAX);
if (seconds % SECONDS_MOD !== 0) throw new TypeError('Expected Number seconds as a multiple of ' + SECONDS_MOD);

return SEQUENCE_LOCKTIME_TYPE_FLAG | (seconds >> SEQUENCE_LOCKTIME_GRANULARITY);
}

// If the input is correct, we return the blocks (no further encoding needed)
if (blocks !== undefined) {
if (!Number.isFinite(blocks)) throw new TypeError('Expected Number blocks');
if (blocks > SEQUENCE_LOCKTIME_MASK) throw new TypeError('Expected Number blocks <= ' + BLOCKS_MAX);

return blocks;
}

// If neither blocks nor seconds are provided, we assume the sequence is final
return SEQUENCE_FINAL;
}
5 changes: 3 additions & 2 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './artifact.js';
export * from './bip68.js';
export * from './bitauth-script.js';
export * from './data.js';
export * from './hash.js';
export * from './script.js';
export * from './types.js';
export * from './source-map.js';
export * from './bitauth-script.js';
export * from './types.js';
5 changes: 2 additions & 3 deletions website/docs/guides/covenants.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ This contract applies similar techniques as the previous two examples to verify

## Local State

Smart contracts which persist for multiple transactions might want to keep data for later use. This is called local state. With the CashTokens upgrade, local state can be kept in the commitment field of the NFT of the smart contract UTXO. Because the state is not kept in the script of the smart contract itself, the address can remain the same.
Smart contracts which persist for multiple transactions might want to keep data for later use. This is called local state. With the CashTokens upgrade, local state can be kept in the commitment field of the NFT of the smart contract UTXO. Because the state is not kept in the script of the smart contract itself, the address can remain the same.

:::info
Covenants can also use 'simulated state', where state is kept in the contract script and the contract enforces a new P2SH locking bytecode of the contract with a different state update. This method causes the contract address to change with each state update.
Expand Down Expand Up @@ -211,7 +211,7 @@ We use `tx.locktime` to introspect the value of the timelock, and to write the v

### Issuing NFTs as receipts

A covenant that manages funds (BCH + fungible tokens of a certain category) which are pooled together from different people often wants to enable its participants to also exit the covenants with their funds. It would be incredibly hard continuously updating a data structure to keep track of which address contributed how much in the local state of the contract. A much better solution is to issue receipts each time funds are added to the pool! This way the contract does not have a 'global view' of who owns what at any time, but it can validate the receipts when invoking a withdrawal.
A covenant that manages funds (BCH + fungible tokens of a certain category) which are pooled together from different people often wants to enable its participants to also exit the covenants with their funds. It would be incredibly hard continuously updating a data structure to keep track of which address contributed how much in the local state of the contract. A much better solution is to issue receipts each time funds are added to the pool! This way the contract does not have a 'global view' of who owns what at any time, but it can validate the receipts when invoking a withdrawal.

Technically this happens by minting a new NFT, with in the commitment field the amount of satoshis or fungible tokens that were contributed to the pool, and sending this to the address of the contributor.

Expand Down Expand Up @@ -320,4 +320,3 @@ We have discussed the main uses for covenants as they exist on Bitcoin Cash toda
Keeping local state in NFTs and issuing NFTs as receipts are two strategies which can be used to create much more sophisticated decentralized applications such as the AMM-style DEX named [Jedex](https://blog.bitjson.com/jedex-decentralized-exchanges-on-bitcoin-cash/).

[bitcoin-covenants]: https://fc16.ifca.ai/bitcoin/papers/MES16.pdf
[bip68]: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
17 changes: 1 addition & 16 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3405,28 +3405,13 @@ bip39@^3.0.2:
pbkdf2 "^3.0.9"
randombytes "^2.0.1"

bip39@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0"
integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==
dependencies:
"@types/node" "11.11.6"
create-hash "^1.1.0"
pbkdf2 "^3.0.9"
randombytes "^2.0.1"

bip66@^1.1.0, bip66@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
integrity sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=
dependencies:
safe-buffer "^5.0.1"

bip68@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/bip68/-/bip68-1.0.4.tgz#78a95c7a43fad183957995cc2e08d79b0c372c4d"
integrity sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==

bitcoinjs-message@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bitcoinjs-message/-/bitcoinjs-message-2.1.1.tgz#c55d78f4461691b77fa5f9341216f8cd7ae0d0f4"
Expand Down Expand Up @@ -6566,7 +6551,7 @@ hash-base@^3.0.0:
readable-stream "^3.6.0"
safe-buffer "^5.2.0"

hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7:
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
Expand Down

0 comments on commit 188537d

Please sign in to comment.