-
Notifications
You must be signed in to change notification settings - Fork 36
WIP: ARCs, Relays, and Hubs #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
martindale
wants to merge
12
commits into
master
Choose a base branch
from
feature/rsi
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
92b5dc8
Update package-lock.json
martindale 67d6edf
Add .npmrc and address git install limitations
martindale 91f0342
Expand Contract capabilities from downstream work
martindale 3a4d87a
Add test coverage for contract capabilities
martindale b984c1f
Expand contract test coverage further
martindale 3fc4b66
Update API
martindale a97e752
Prepare downstream ARC path
martindale c39fcdb
Address peer connection issues
martindale dba7d45
Begin tightening on core protocol
martindale 1d1d421
Begin sealing group chats
martindale 5878630
Consolidate various contract message behaviors
martindale da3913b
General Machine sweep
martindale File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # npm 12+ defaults allow-git=none. | ||
| # @fabric/core itself has no Fabric git dependencies, but consumers that install | ||
| # core/http/hub from GitHub need `allow-git=all` so nested git dep preparation | ||
| # (commit-SHA fetches) is not refused. Keep this file so the monorepo default | ||
| # matches Hub / http / app packages. | ||
| allow-git=all | ||
|
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
cursor[bot] marked this conversation as resolved.
Outdated
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Contract-scoped Token capabilities (read-only member vs signer). | ||
| */ | ||
|
|
||
| const Key = require('../types/key'); | ||
| const Token = require('../types/token'); | ||
|
|
||
| const OP_CONTRACT_READ = 'OP_CONTRACT_READ'; | ||
| const OP_CONTRACT_SIGN = 'OP_CONTRACT_SIGN'; | ||
|
|
||
| /** | ||
| * @param {object} opts | ||
| * @param {object|Key} opts.issuerKey Fabric Key (or settings) of issuer | ||
| * @param {string} opts.subject Subject compressed pubkey | ||
| * @param {string} opts.contractId Contract / namespace id | ||
| * @param {string} [opts.capability=OP_CONTRACT_READ] | ||
| * @param {number} [opts.expiresInSeconds] | ||
| * @returns {string} Token.toSignedString() | ||
| */ | ||
| function issueContractCapability (opts = {}) { | ||
| const capability = opts.capability || OP_CONTRACT_READ; | ||
| if (capability !== OP_CONTRACT_READ && capability !== OP_CONTRACT_SIGN) { | ||
| throw new Error(`unsupported contract capability: ${capability}`); | ||
| } | ||
| const contractId = String(opts.contractId || '').trim().toLowerCase(); | ||
| if (!contractId) throw new Error('contractId required'); | ||
| const subject = String(opts.subject || '').trim().toLowerCase(); | ||
| if (!subject) throw new Error('subject required'); | ||
| const issuer = opts.issuerKey && typeof opts.issuerKey.sign === 'function' | ||
| ? opts.issuerKey | ||
| : new Key(opts.issuerKey || {}); | ||
| const token = new Token({ | ||
| capability, | ||
| issuer, | ||
| subject, | ||
| ctx: { contractId } | ||
| }); | ||
| return token.toSignedString({ | ||
| expiresInSeconds: opts.expiresInSeconds, | ||
| ctx: { contractId } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} tokenString | ||
| * @param {object} expect | ||
| * @param {string} expect.contractId | ||
| * @param {string} [expect.expectedCap] | ||
| * @param {string} [expect.subject] | ||
| * @param {Key|object} [expect.issuerKey] If set, verify Schnorr against this key | ||
| * @returns {{ cap: string, iss: string, sub: string, iat: number, exp: number, ctx?: object }|null} | ||
| */ | ||
| function verifyContractCapability (tokenString, expect = {}) { | ||
| const contractId = String(expect.contractId || '').trim().toLowerCase(); | ||
| if (!contractId) return null; | ||
| let payload = null; | ||
| if (expect.issuerKey) { | ||
| const key = expect.issuerKey && typeof expect.issuerKey.verify === 'function' | ||
| ? expect.issuerKey | ||
| : new Key(expect.issuerKey); | ||
| payload = Token.verifySigned(tokenString, key); | ||
| } else { | ||
| // Parse without issuer check (caller may only need shape); still require sig format. | ||
| if (!tokenString || typeof tokenString !== 'string') return null; | ||
| const parts = tokenString.split('.'); | ||
| if (parts.length !== 2) return null; | ||
| try { | ||
| const { tryParseWireJson } = require('./wireJson'); | ||
| const payloadStr = Token.base64UrlDecode(parts[0]); | ||
| const pr = tryParseWireJson(payloadStr); | ||
| if (!pr.ok) return null; | ||
| payload = pr.value; | ||
| if (!payload || payload.exp == null || Date.now() / 1000 > payload.exp) return null; | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| if (!payload) return null; | ||
| const ctxId = payload.ctx && payload.ctx.contractId | ||
| ? String(payload.ctx.contractId).trim().toLowerCase() | ||
| : ''; | ||
| if (ctxId !== contractId) return null; | ||
| if (expect.expectedCap && payload.cap !== expect.expectedCap) return null; | ||
| if (expect.subject) { | ||
| const sub = String(payload.sub || '').trim().toLowerCase(); | ||
| if (sub !== String(expect.subject).trim().toLowerCase()) return null; | ||
| } | ||
| return payload; | ||
| } | ||
|
|
||
| function roleToCapability (role) { | ||
| const r = String(role || '').toLowerCase(); | ||
| if (r === 'signer' || r === 'sign') return OP_CONTRACT_SIGN; | ||
| return OP_CONTRACT_READ; | ||
| } | ||
|
|
||
| function capabilityToRole (cap) { | ||
| if (cap === OP_CONTRACT_SIGN) return 'signer'; | ||
| if (cap === OP_CONTRACT_READ) return 'reader'; | ||
| return null; | ||
| } | ||
|
|
||
| module.exports = { | ||
| OP_CONTRACT_READ, | ||
| OP_CONTRACT_SIGN, | ||
| issueContractCapability, | ||
| verifyContractCapability, | ||
| roleToCapability, | ||
| capabilityToRole | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Contract-namespace tip attestation (k-of-n Schnorr). | ||
| * | ||
| * Same witness shape as {@link beaconFederationSigning}: members / validators | ||
| * Schnorr-sign a canonical tip string. Used by GoonCitizen Group Statechain | ||
| * journals today; Hub contract sidechains SHOULD reuse this when sealing | ||
| * per-namespace tips (keep Hub docs / RPC in sync if the tip kind changes). | ||
| * | ||
| * @see functions/beaconFederationSigning.js | ||
| * @see docs/APPLICATION_NAMESPACES.md | ||
| * @see docs/DISTRIBUTED_EXECUTION.md | ||
| */ | ||
|
|
||
| const crypto = require('crypto'); | ||
| const Key = require('../types/key'); | ||
| const fabricCanonicalJson = require('./fabricCanonicalJson'); | ||
| const { verifyFederationWitnessOnMessage } = require('./beaconFederationSigning'); | ||
|
|
||
| /** Canonical tip kind — Hub and apps must agree; bump only with protocol note. */ | ||
| const CONTRACT_STATE_TIP_KIND = 'ContractStateTip'; | ||
|
|
||
| /** | ||
| * UTF-8 string members Schnorr-sign for a contract-namespace tip. | ||
| * @param {object} fields | ||
| * @param {string} fields.contractId | ||
| * @param {number} fields.clock | ||
| * @param {string} fields.stateDigest | ||
| * @returns {string} | ||
| */ | ||
| function signingStringForContractStateTip (fields = {}) { | ||
| return fabricCanonicalJson({ | ||
| version: 1, | ||
| kind: CONTRACT_STATE_TIP_KIND, | ||
| contractId: String(fields.contractId || '').trim().toLowerCase(), | ||
| clock: Number(fields.clock) || 0, | ||
| stateDigest: String(fields.stateDigest || '').trim().toLowerCase() | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} contractId | ||
| * @param {number} clock | ||
| * @param {string} stateDigest | ||
| * @returns {Buffer} | ||
| */ | ||
| function tipMessageBuffer (contractId, clock, stateDigest) { | ||
| return Buffer.from(signingStringForContractStateTip({ contractId, clock, stateDigest }), 'utf8'); | ||
| } | ||
|
|
||
| /** | ||
| * Sign a tip with a Fabric Key (or `{ xprv }` / Key-like). | ||
| * @param {object} keyOrSettings Key instance or settings for `new Key(...)` | ||
| * @param {string} contractId | ||
| * @param {number} clock | ||
| * @param {string} stateDigest | ||
| * @returns {{ pubkey: string, signature: string, message: string }} | ||
| */ | ||
| function signContractStateTip (keyOrSettings, contractId, clock, stateDigest) { | ||
| const key = keyOrSettings && typeof keyOrSettings.signSchnorr === 'function' | ||
| ? keyOrSettings | ||
| : new Key(keyOrSettings || {}); | ||
| const message = signingStringForContractStateTip({ contractId, clock, stateDigest }); | ||
| const signature = Buffer.from(key.signSchnorr(Buffer.from(message, 'utf8'))).toString('hex'); | ||
| return { pubkey: key.pubkey, signature, message }; | ||
| } | ||
|
|
||
| /** | ||
| * Verify k-of-n tip signatures (Federation witness shape). | ||
| * @param {string[]} validatorPubkeys | ||
| * @param {number} threshold | ||
| * @param {string} contractId | ||
| * @param {number} clock | ||
| * @param {string} stateDigest | ||
| * @param {{ [pubkey: string]: string }|{ signatures: object }} signaturesOrWitness | ||
| * @returns {boolean} | ||
| */ | ||
| function verifyContractStateTip ( | ||
| validatorPubkeys, | ||
| threshold, | ||
| contractId, | ||
| clock, | ||
| stateDigest, | ||
| signaturesOrWitness | ||
| ) { | ||
| const witness = signaturesOrWitness && signaturesOrWitness.signatures | ||
| && typeof signaturesOrWitness.signatures === 'object' | ||
| ? signaturesOrWitness | ||
| : { signatures: signaturesOrWitness || {} }; | ||
| return verifyFederationWitnessOnMessage( | ||
| tipMessageBuffer(contractId, clock, stateDigest), | ||
| witness, | ||
| validatorPubkeys, | ||
| threshold | ||
| ); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Merge signature maps (pubkey → sig hex), last write wins per key. | ||
| * @param {...object} maps | ||
| * @returns {{ [pubkey: string]: string }} | ||
| */ | ||
| function mergeTipSignatures (...maps) { | ||
| const out = {}; | ||
| for (const m of maps) { | ||
| if (!m || typeof m !== 'object') continue; | ||
| const src = m.signatures && typeof m.signatures === 'object' ? m.signatures : m; | ||
| for (const [pk, sig] of Object.entries(src)) { | ||
| if (typeof pk === 'string' && pk && typeof sig === 'string' && sig) { | ||
| out[pk] = sig; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
|
|
||
| /** | ||
| * Stable digest helper for tests / logging. | ||
| * @param {string} contractId | ||
| * @param {number} clock | ||
| * @param {string} stateDigest | ||
| * @returns {string} hex sha256 of tip message | ||
| */ | ||
| function tipDigestHex (contractId, clock, stateDigest) { | ||
| return crypto.createHash('sha256') | ||
| .update(tipMessageBuffer(contractId, clock, stateDigest)) | ||
| .digest('hex'); | ||
| } | ||
|
|
||
| module.exports = { | ||
| CONTRACT_STATE_TIP_KIND, | ||
| signingStringForContractStateTip, | ||
| tipMessageBuffer, | ||
| signContractStateTip, | ||
| verifyContractStateTip, | ||
| mergeTipSignatures, | ||
| tipDigestHex | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.